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.app.ActivityManager; 28 import android.os.Handler; 29 import android.view.MotionEvent; 30 31 import com.android.launcher3.AbstractFloatingView; 32 import com.android.launcher3.Launcher; 33 import com.android.launcher3.R; 34 import com.android.launcher3.compat.UserManagerCompat; 35 import com.android.launcher3.states.InternalStateHandler; 36 37 /** 38 * Abstract base class of floating view responsible for showing discovery bounce animation 39 */ 40 public class DiscoveryBounce extends AbstractFloatingView { 41 42 private static final long DELAY_MS = 450; 43 44 public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown"; 45 public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen"; 46 47 private final Launcher mLauncher; 48 private final Animator mDiscoBounceAnimation; 49 50 public DiscoveryBounce(Launcher launcher, float delta) { 51 super(launcher, null); 52 mLauncher = launcher; 53 AllAppsTransitionController controller = mLauncher.getAllAppsController(); 54 55 mDiscoBounceAnimation = 56 AnimatorInflater.loadAnimator(launcher, R.animator.discovery_bounce); 57 mDiscoBounceAnimation.setTarget(new VerticalProgressWrapper(controller, delta)); 58 mDiscoBounceAnimation.addListener(new AnimatorListenerAdapter() { 59 @Override 60 public void onAnimationEnd(Animator animation) { 61 handleClose(false); 62 } 63 }); 64 mDiscoBounceAnimation.addListener(controller.getProgressAnimatorListener()); 65 } 66 67 @Override 68 protected void onAttachedToWindow() { 69 super.onAttachedToWindow(); 70 mDiscoBounceAnimation.start(); 71 } 72 73 @Override 74 protected void onDetachedFromWindow() { 75 super.onDetachedFromWindow(); 76 if (mDiscoBounceAnimation.isRunning()) { 77 mDiscoBounceAnimation.end(); 78 } 79 } 80 81 @Override 82 public boolean onBackPressed() { 83 super.onBackPressed(); 84 // Go back to the previous state (from a user's perspective this floating view isn't 85 // something to go back from). 86 return false; 87 } 88 89 @Override 90 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 91 handleClose(false); 92 return false; 93 } 94 95 @Override 96 protected void handleClose(boolean animate) { 97 if (mIsOpen) { 98 mIsOpen = false; 99 mLauncher.getDragLayer().removeView(this); 100 // Reset the all-apps progress to what ever it was previously. 101 mLauncher.getAllAppsController().setProgress(mLauncher.getStateManager() 102 .getState().getVerticalProgress(mLauncher)); 103 } 104 } 105 106 @Override 107 public void logActionCommand(int command) { 108 // Since this is on-boarding popup, it is not a user controlled action. 109 } 110 111 @Override 112 protected boolean isOfType(int type) { 113 return (type & TYPE_DISCOVERY_BOUNCE) != 0; 114 } 115 116 private void show(int containerType) { 117 mIsOpen = true; 118 mLauncher.getDragLayer().addView(this); 119 mLauncher.getUserEventDispatcher().logActionBounceTip(containerType); 120 } 121 122 public static void showForHomeIfNeeded(Launcher launcher) { 123 showForHomeIfNeeded(launcher, true); 124 } 125 126 private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) { 127 if (!launcher.isInState(NORMAL) 128 || launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false) 129 || AbstractFloatingView.getTopOpenView(launcher) != null 130 || UserManagerCompat.getInstance(launcher).isDemoUser() 131 || ActivityManager.isRunningInTestHarness()) { 132 return; 133 } 134 135 if (withDelay) { 136 new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS); 137 return; 138 } 139 140 new DiscoveryBounce(launcher, 0).show(HOTSEAT); 141 } 142 143 public static void showForOverviewIfNeeded(Launcher launcher) { 144 showForOverviewIfNeeded(launcher, true); 145 } 146 147 private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay) { 148 if (!launcher.isInState(OVERVIEW) 149 || !launcher.hasBeenResumed() 150 || launcher.isForceInvisible() 151 || launcher.getDeviceProfile().isVerticalBarLayout() 152 || launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false) 153 || UserManagerCompat.getInstance(launcher).isDemoUser() 154 || ActivityManager.isRunningInTestHarness()) { 155 return; 156 } 157 158 if (withDelay) { 159 new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS); 160 return; 161 } else if (InternalStateHandler.hasPending() 162 || AbstractFloatingView.getTopOpenView(launcher) != null) { 163 // TODO: Move these checks to the top and call this method after invalidate handler. 164 return; 165 } 166 167 new DiscoveryBounce(launcher, (1 - OVERVIEW.getVerticalProgress(launcher))) 168 .show(PREDICTION); 169 } 170 171 /** 172 * A wrapper around {@link AllAppsTransitionController} allowing a fixed shift in the value. 173 */ 174 public static class VerticalProgressWrapper { 175 176 private final float mDelta; 177 private final AllAppsTransitionController mController; 178 179 private VerticalProgressWrapper(AllAppsTransitionController controller, float delta) { 180 mController = controller; 181 mDelta = delta; 182 } 183 184 public float getProgress() { 185 return mController.getProgress() + mDelta; 186 } 187 188 public void setProgress(float progress) { 189 mController.setProgress(progress - mDelta); 190 } 191 } 192 } 193