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 21 import android.animation.Animator; 22 import android.animation.AnimatorInflater; 23 import android.os.Handler; 24 import android.os.UserManager; 25 import android.view.MotionEvent; 26 import android.view.View; 27 28 import com.android.launcher3.AbstractFloatingView; 29 import com.android.launcher3.Launcher; 30 import com.android.launcher3.LauncherState; 31 import com.android.launcher3.R; 32 import com.android.launcher3.Utilities; 33 import com.android.launcher3.anim.AnimatorListeners; 34 import com.android.launcher3.statemanager.StateManager.StateListener; 35 import com.android.launcher3.util.OnboardingPrefs; 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 private final Launcher mLauncher; 45 private final Animator mDiscoBounceAnimation; 46 47 private final StateListener<LauncherState> mStateListener = new StateListener<LauncherState>() { 48 @Override 49 public void onStateTransitionStart(LauncherState toState) { 50 handleClose(false); 51 } 52 53 @Override 54 public void onStateTransitionComplete(LauncherState finalState) {} 55 }; 56 DiscoveryBounce(Launcher launcher)57 public DiscoveryBounce(Launcher launcher) { 58 super(launcher, null); 59 mLauncher = launcher; 60 61 mDiscoBounceAnimation = 62 AnimatorInflater.loadAnimator(launcher, R.animator.discovery_bounce); 63 mDiscoBounceAnimation.setTarget(new VerticalProgressWrapper( 64 launcher.getHotseat(), mLauncher.getDragLayer().getHeight())); 65 mDiscoBounceAnimation.addListener(AnimatorListeners.forEndCallback(this::handleClose)); 66 launcher.getStateManager().addStateListener(mStateListener); 67 } 68 69 @Override onAttachedToWindow()70 protected void onAttachedToWindow() { 71 super.onAttachedToWindow(); 72 mDiscoBounceAnimation.start(); 73 } 74 75 @Override onDetachedFromWindow()76 protected void onDetachedFromWindow() { 77 super.onDetachedFromWindow(); 78 if (mDiscoBounceAnimation.isRunning()) { 79 mDiscoBounceAnimation.end(); 80 } 81 } 82 83 @Override onBackPressed()84 public boolean onBackPressed() { 85 super.onBackPressed(); 86 // Go back to the previous state (from a user's perspective this floating view isn't 87 // something to go back from). 88 return false; 89 } 90 91 @Override onControllerInterceptTouchEvent(MotionEvent ev)92 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 93 handleClose(false); 94 return false; 95 } 96 97 @Override handleClose(boolean animate)98 protected void handleClose(boolean animate) { 99 if (mIsOpen) { 100 mIsOpen = false; 101 mLauncher.getDragLayer().removeView(this); 102 // Reset the translation to what ever it was previously. 103 mLauncher.getHotseat().setTranslationY(mLauncher.getStateManager().getState() 104 .getHotseatScaleAndTranslation(mLauncher).translationY); 105 mLauncher.getStateManager().removeStateListener(mStateListener); 106 } 107 } 108 109 @Override isOfType(int type)110 protected boolean isOfType(int type) { 111 return (type & TYPE_DISCOVERY_BOUNCE) != 0; 112 } 113 show()114 private void show() { 115 mIsOpen = true; 116 mLauncher.getDragLayer().addView(this); 117 // TODO: add WW log for discovery bounce tip show event. 118 } 119 showForHomeIfNeeded(Launcher launcher)120 public static void showForHomeIfNeeded(Launcher launcher) { 121 showForHomeIfNeeded(launcher, true); 122 } 123 showForHomeIfNeeded(Launcher launcher, boolean withDelay)124 private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) { 125 OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs(); 126 if (!launcher.isInState(NORMAL) 127 || onboardingPrefs.getBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN) 128 || AbstractFloatingView.getTopOpenView(launcher) != null 129 || launcher.getSystemService(UserManager.class).isDemoUser() 130 || Utilities.IS_RUNNING_IN_TEST_HARNESS) { 131 return; 132 } 133 134 if (withDelay) { 135 new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS); 136 return; 137 } 138 onboardingPrefs.incrementEventCount(OnboardingPrefs.HOME_BOUNCE_COUNT); 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