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