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.util.OnboardingPrefs.HOME_BOUNCE_SEEN; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorInflater; 24 import android.os.Handler; 25 import android.os.UserManager; 26 import android.view.MotionEvent; 27 import android.view.View; 28 29 import com.android.launcher3.AbstractFloatingView; 30 import com.android.launcher3.Launcher; 31 import com.android.launcher3.LauncherState; 32 import com.android.launcher3.R; 33 import com.android.launcher3.Utilities; 34 import com.android.launcher3.anim.AnimatorListeners; 35 import com.android.launcher3.statemanager.StateManager.StateListener; 36 import com.android.launcher3.util.OnboardingPrefs; 37 38 /** 39 * Abstract base class of floating view responsible for showing discovery bounce animation 40 */ 41 public class DiscoveryBounce extends AbstractFloatingView { 42 43 private static final long DELAY_MS = 450; 44 45 private final Launcher mLauncher; 46 private final Animator mDiscoBounceAnimation; 47 48 private final StateListener<LauncherState> mStateListener = new StateListener<LauncherState>() { 49 @Override 50 public void onStateTransitionStart(LauncherState toState) { 51 handleClose(false); 52 } 53 54 @Override 55 public void onStateTransitionComplete(LauncherState finalState) {} 56 }; 57 DiscoveryBounce(Launcher launcher)58 public DiscoveryBounce(Launcher launcher) { 59 super(launcher, null); 60 mLauncher = launcher; 61 62 mDiscoBounceAnimation = 63 AnimatorInflater.loadAnimator(launcher, R.animator.discovery_bounce); 64 mDiscoBounceAnimation.setTarget(new VerticalProgressWrapper( 65 launcher.getHotseat(), mLauncher.getDragLayer().getHeight())); 66 mDiscoBounceAnimation.addListener(AnimatorListeners.forEndCallback(this::handleClose)); 67 launcher.getStateManager().addStateListener(mStateListener); 68 } 69 70 @Override onAttachedToWindow()71 protected void onAttachedToWindow() { 72 super.onAttachedToWindow(); 73 mDiscoBounceAnimation.start(); 74 } 75 76 @Override onDetachedFromWindow()77 protected void onDetachedFromWindow() { 78 super.onDetachedFromWindow(); 79 if (mDiscoBounceAnimation.isRunning()) { 80 mDiscoBounceAnimation.end(); 81 } 82 } 83 84 @Override canHandleBack()85 public boolean canHandleBack() { 86 // Since DiscoveryBounce doesn't handle back, onBackInvoked() won't be called and we should 87 // close it without animation. 88 close(false); 89 return false; 90 } 91 92 @Override onControllerInterceptTouchEvent(MotionEvent ev)93 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 94 handleClose(false); 95 return false; 96 } 97 98 @Override handleClose(boolean animate)99 protected void handleClose(boolean animate) { 100 if (mIsOpen) { 101 mIsOpen = false; 102 mLauncher.getDragLayer().removeView(this); 103 // Reset the translation to what ever it was previously. 104 mLauncher.getHotseat().setTranslationY(mLauncher.getStateManager().getState() 105 .getHotseatScaleAndTranslation(mLauncher).translationY); 106 mLauncher.getStateManager().removeStateListener(mStateListener); 107 } 108 } 109 110 @Override isOfType(int type)111 protected boolean isOfType(int type) { 112 return (type & TYPE_DISCOVERY_BOUNCE) != 0; 113 } 114 show()115 private void show() { 116 mIsOpen = true; 117 mLauncher.getDragLayer().addView(this); 118 // TODO: add WW log for discovery bounce tip show event. 119 } 120 showForHomeIfNeeded(Launcher launcher)121 public static void showForHomeIfNeeded(Launcher launcher) { 122 showForHomeIfNeeded(launcher, true); 123 } 124 showForHomeIfNeeded(Launcher launcher, boolean withDelay)125 private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) { 126 if (!launcher.isInState(NORMAL) 127 || HOME_BOUNCE_SEEN.get(launcher) 128 || AbstractFloatingView.getTopOpenView(launcher) != null 129 || launcher.getSystemService(UserManager.class).isDemoUser() 130 || Utilities.isRunningInTestHarness()) { 131 return; 132 } 133 134 if (withDelay) { 135 new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS); 136 return; 137 } 138 OnboardingPrefs.HOME_BOUNCE_COUNT.increment(launcher); 139 new DiscoveryBounce(launcher).show(); 140 } 141 142 /** 143 * A wrapper around hotseat animator allowing a fixed shift in the value. 144 */ 145 public static class VerticalProgressWrapper { 146 147 private final View mView; 148 private final float mLimit; 149 VerticalProgressWrapper(View view, float limit)150 private VerticalProgressWrapper(View view, float limit) { 151 mView = view; 152 mLimit = limit; 153 } 154 getProgress()155 public float getProgress() { 156 return 1 + mView.getTranslationY() / mLimit; 157 } 158 setProgress(float progress)159 public void setProgress(float progress) { 160 mView.setTranslationY(mLimit * (progress - 1)); 161 } 162 } 163 } 164