1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.allapps;
18 
19 import static com.android.launcher3.LauncherState.NORMAL;
20 import static com.android.launcher3.LauncherState.OVERVIEW;
21 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.HOTSEAT;
22 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.PREDICTION;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorInflater;
26 import android.animation.AnimatorListenerAdapter;
27 import android.os.Handler;
28 import android.os.UserManager;
29 import android.view.MotionEvent;
30 
31 import com.android.launcher3.AbstractFloatingView;
32 import com.android.launcher3.Launcher;
33 import com.android.launcher3.LauncherState;
34 import com.android.launcher3.R;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.statemanager.StateManager.StateListener;
37 import com.android.launcher3.touch.PagedOrientationHandler;
38 import com.android.launcher3.util.OnboardingPrefs;
39 
40 /**
41  * Abstract base class of floating view responsible for showing discovery bounce animation
42  */
43 public class DiscoveryBounce extends AbstractFloatingView {
44 
45     private static final long DELAY_MS = 450;
46 
47     private final Launcher mLauncher;
48     private final Animator mDiscoBounceAnimation;
49 
50     private final StateListener<LauncherState> mStateListener = new StateListener<LauncherState>() {
51         @Override
52         public void onStateTransitionStart(LauncherState toState) {
53             handleClose(false);
54         }
55 
56         @Override
57         public void onStateTransitionComplete(LauncherState finalState) {}
58     };
59 
DiscoveryBounce(Launcher launcher, float delta)60     public DiscoveryBounce(Launcher launcher, float delta) {
61         super(launcher, null);
62         mLauncher = launcher;
63         AllAppsTransitionController controller = mLauncher.getAllAppsController();
64 
65         mDiscoBounceAnimation =
66                 AnimatorInflater.loadAnimator(launcher, R.animator.discovery_bounce);
67         mDiscoBounceAnimation.setTarget(new VerticalProgressWrapper(controller, delta));
68         mDiscoBounceAnimation.addListener(new AnimatorListenerAdapter() {
69             @Override
70             public void onAnimationEnd(Animator animation) {
71                 handleClose(false);
72             }
73         });
74         mDiscoBounceAnimation.addListener(controller.getProgressAnimatorListener());
75         launcher.getStateManager().addStateListener(mStateListener);
76     }
77 
78     @Override
onAttachedToWindow()79     protected void onAttachedToWindow() {
80         super.onAttachedToWindow();
81         mDiscoBounceAnimation.start();
82     }
83 
84     @Override
onDetachedFromWindow()85     protected void onDetachedFromWindow() {
86         super.onDetachedFromWindow();
87         if (mDiscoBounceAnimation.isRunning()) {
88             mDiscoBounceAnimation.end();
89         }
90     }
91 
92     @Override
onBackPressed()93     public boolean onBackPressed() {
94         super.onBackPressed();
95         // Go back to the previous state (from a user's perspective this floating view isn't
96         // something to go back from).
97         return false;
98     }
99 
100     @Override
onControllerInterceptTouchEvent(MotionEvent ev)101     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
102         handleClose(false);
103         return false;
104     }
105 
106     @Override
handleClose(boolean animate)107     protected void handleClose(boolean animate) {
108         if (mIsOpen) {
109             mIsOpen = false;
110             mLauncher.getDragLayer().removeView(this);
111             // Reset the all-apps progress to what ever it was previously.
112             mLauncher.getAllAppsController().setProgress(mLauncher.getStateManager()
113                     .getState().getVerticalProgress(mLauncher));
114             mLauncher.getStateManager().removeStateListener(mStateListener);
115         }
116     }
117 
118     @Override
logActionCommand(int command)119     public void logActionCommand(int command) {
120         // Since this is on-boarding popup, it is not a user controlled action.
121     }
122 
123     @Override
isOfType(int type)124     protected boolean isOfType(int type) {
125         return (type & TYPE_DISCOVERY_BOUNCE) != 0;
126     }
127 
show(int containerType)128     private void show(int containerType) {
129         mIsOpen = true;
130         mLauncher.getDragLayer().addView(this);
131         mLauncher.getUserEventDispatcher().logActionBounceTip(containerType);
132     }
133 
showForHomeIfNeeded(Launcher launcher)134     public static void showForHomeIfNeeded(Launcher launcher) {
135         showForHomeIfNeeded(launcher, true);
136     }
137 
showForHomeIfNeeded(Launcher launcher, boolean withDelay)138     private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) {
139         OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs();
140         if (!launcher.isInState(NORMAL)
141                 || onboardingPrefs.getBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN)
142                 || AbstractFloatingView.getTopOpenView(launcher) != null
143                 || launcher.getSystemService(UserManager.class).isDemoUser()
144                 || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
145             return;
146         }
147 
148         if (withDelay) {
149             new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS);
150             return;
151         }
152         onboardingPrefs.incrementEventCount(OnboardingPrefs.HOME_BOUNCE_COUNT);
153 
154         new DiscoveryBounce(launcher, 0).show(HOTSEAT);
155     }
156 
showForOverviewIfNeeded(Launcher launcher, PagedOrientationHandler orientationHandler)157     public static void showForOverviewIfNeeded(Launcher launcher,
158                                                PagedOrientationHandler orientationHandler) {
159         showForOverviewIfNeeded(launcher, true, orientationHandler);
160     }
161 
showForOverviewIfNeeded(Launcher launcher, boolean withDelay, PagedOrientationHandler orientationHandler)162     private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay,
163                                                 PagedOrientationHandler orientationHandler) {
164         OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs();
165         if (!launcher.isInState(OVERVIEW)
166                 || !launcher.hasBeenResumed()
167                 || launcher.isForceInvisible()
168                 || launcher.getDeviceProfile().isVerticalBarLayout()
169                 || !orientationHandler.isLayoutNaturalToLauncher()
170                 || onboardingPrefs.getBoolean(OnboardingPrefs.SHELF_BOUNCE_SEEN)
171                 || launcher.getSystemService(UserManager.class).isDemoUser()
172                 || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
173             return;
174         }
175 
176         if (withDelay) {
177             new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false,
178                     orientationHandler), DELAY_MS);
179             return;
180         } else if (AbstractFloatingView.getTopOpenView(launcher) != null) {
181             // TODO: Move these checks to the top and call this method after invalidate handler.
182             return;
183         }
184         onboardingPrefs.incrementEventCount(OnboardingPrefs.SHELF_BOUNCE_COUNT);
185 
186         new DiscoveryBounce(launcher, (1 - OVERVIEW.getVerticalProgress(launcher)))
187                 .show(PREDICTION);
188     }
189 
190     /**
191      * A wrapper around {@link AllAppsTransitionController} allowing a fixed shift in the value.
192      */
193     public static class VerticalProgressWrapper {
194 
195         private final float mDelta;
196         private final AllAppsTransitionController mController;
197 
VerticalProgressWrapper(AllAppsTransitionController controller, float delta)198         private VerticalProgressWrapper(AllAppsTransitionController controller, float delta) {
199             mController = controller;
200             mDelta = delta;
201         }
202 
getProgress()203         public float getProgress() {
204             return mController.getProgress() + mDelta;
205         }
206 
setProgress(float progress)207         public void setProgress(float progress) {
208             mController.setProgress(progress - mDelta);
209         }
210     }
211 }
212