1 /* 2 * Copyright (C) 2020 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 package com.android.quickstep.views; 17 18 import static com.android.launcher3.LauncherState.ALL_APPS; 19 import static com.android.launcher3.anim.Interpolators.ACCEL; 20 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 21 import static com.android.launcher3.anim.Interpolators.LINEAR; 22 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; 23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN; 24 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 25 26 import android.animation.Animator; 27 import android.animation.AnimatorListenerAdapter; 28 import android.animation.AnimatorSet; 29 import android.animation.ValueAnimator; 30 import android.content.Context; 31 import android.graphics.Canvas; 32 import android.graphics.Rect; 33 import android.graphics.drawable.GradientDrawable; 34 import android.util.AttributeSet; 35 import android.view.MotionEvent; 36 import android.view.ViewGroup; 37 38 import androidx.core.graphics.ColorUtils; 39 40 import com.android.launcher3.AbstractFloatingView; 41 import com.android.launcher3.DeviceProfile; 42 import com.android.launcher3.Launcher; 43 import com.android.launcher3.R; 44 import com.android.launcher3.Utilities; 45 import com.android.launcher3.anim.AnimatorPlaybackController; 46 import com.android.launcher3.anim.Interpolators; 47 import com.android.launcher3.dragndrop.DragLayer; 48 import com.android.launcher3.states.StateAnimationConfig; 49 import com.android.launcher3.util.Themes; 50 import com.android.quickstep.util.MultiValueUpdateListener; 51 52 /** 53 * View used to educate the user on how to access All Apps when in No Nav Button navigation mode. 54 */ 55 public class AllAppsEduView extends AbstractFloatingView { 56 57 private Launcher mLauncher; 58 59 private AnimatorSet mAnimation; 60 61 private GradientDrawable mCircle; 62 private GradientDrawable mGradient; 63 64 private int mCircleSizePx; 65 private int mPaddingPx; 66 private int mWidthPx; 67 private int mMaxHeightPx; 68 AllAppsEduView(Context context, AttributeSet attrs)69 public AllAppsEduView(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 mCircle = (GradientDrawable) context.getDrawable(R.drawable.all_apps_edu_circle); 72 mCircleSizePx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_circle_size); 73 mPaddingPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_padding); 74 mWidthPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_width); 75 mMaxHeightPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_max_height); 76 setWillNotDraw(false); 77 } 78 79 @Override onDraw(Canvas canvas)80 protected void onDraw(Canvas canvas) { 81 super.onDraw(canvas); 82 mGradient.draw(canvas); 83 mCircle.draw(canvas); 84 } 85 86 @Override onAttachedToWindow()87 protected void onAttachedToWindow() { 88 super.onAttachedToWindow(); 89 mIsOpen = true; 90 } 91 92 @Override onDetachedFromWindow()93 protected void onDetachedFromWindow() { 94 super.onDetachedFromWindow(); 95 mIsOpen = false; 96 } 97 98 @Override handleClose(boolean animate)99 protected void handleClose(boolean animate) { 100 mLauncher.getDragLayer().removeView(this); 101 } 102 103 @Override logActionCommand(int command)104 public void logActionCommand(int command) { 105 // TODO 106 } 107 108 @Override isOfType(int type)109 protected boolean isOfType(int type) { 110 return (type & TYPE_ALL_APPS_EDU) != 0; 111 } 112 113 @Override onControllerInterceptTouchEvent(MotionEvent ev)114 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 115 return mAnimation != null && mAnimation.isRunning(); 116 } 117 playAnimation()118 private void playAnimation() { 119 if (mAnimation != null) { 120 return; 121 } 122 mAnimation = new AnimatorSet(); 123 124 final Rect circleBoundsOg = new Rect(mCircle.getBounds()); 125 final Rect gradientBoundsOg = new Rect(mGradient.getBounds()); 126 final Rect temp = new Rect(); 127 final float transY = mMaxHeightPx - mCircleSizePx - mPaddingPx; 128 129 // 1st: Circle alpha/scale 130 int firstPart = 600; 131 // 2nd: Circle animates upwards, Gradient alpha fades in, Gradient grows, All Apps hint 132 int secondPart = 1200; 133 int introDuration = firstPart + secondPart; 134 135 StateAnimationConfig config = new StateAnimationConfig(); 136 config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL, 137 0, 0.08f)); 138 config.duration = secondPart; 139 config.userControlled = false; 140 AnimatorPlaybackController stateAnimationController = 141 mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, config); 142 float maxAllAppsProgress = 0.15f; 143 144 ValueAnimator intro = ValueAnimator.ofFloat(0, 1f); 145 intro.setInterpolator(LINEAR); 146 intro.setDuration(introDuration); 147 intro.addUpdateListener((new MultiValueUpdateListener() { 148 FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR); 149 FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7); 150 FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN); 151 FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR); 152 153 @Override 154 public void onUpdate(float progress) { 155 temp.set(circleBoundsOg); 156 temp.offset(0, (int) -mDeltaY.value); 157 Utilities.scaleRectAboutCenter(temp, mCircleScale.value); 158 mCircle.setBounds(temp); 159 mCircle.setAlpha((int) mCircleAlpha.value); 160 mGradient.setAlpha((int) mGradientAlpha.value); 161 162 temp.set(gradientBoundsOg); 163 temp.top -= mDeltaY.value; 164 mGradient.setBounds(temp); 165 invalidate(); 166 167 float stateProgress = Utilities.mapToRange(mDeltaY.value, 0, transY, 0, 168 maxAllAppsProgress, LINEAR); 169 stateAnimationController.setPlayFraction(stateProgress); 170 } 171 })); 172 intro.addListener(new AnimatorListenerAdapter() { 173 @Override 174 public void onAnimationEnd(Animator animation) { 175 mCircle.setAlpha(0); 176 mGradient.setAlpha(0); 177 } 178 }); 179 mAnimation.play(intro); 180 181 ValueAnimator closeAllApps = ValueAnimator.ofFloat(maxAllAppsProgress, 0f); 182 closeAllApps.addUpdateListener(valueAnimator -> { 183 stateAnimationController.setPlayFraction((float) valueAnimator.getAnimatedValue()); 184 }); 185 closeAllApps.setInterpolator(FAST_OUT_SLOW_IN); 186 closeAllApps.setStartDelay(introDuration); 187 closeAllApps.setDuration(250); 188 mAnimation.play(closeAllApps); 189 190 mAnimation.addListener(new AnimatorListenerAdapter() { 191 @Override 192 public void onAnimationEnd(Animator animation) { 193 mAnimation = null; 194 stateAnimationController.dispatchOnCancel(); 195 handleClose(false); 196 } 197 }); 198 mAnimation.start(); 199 } 200 init(Launcher launcher)201 private void init(Launcher launcher) { 202 mLauncher = launcher; 203 204 int accentColor = Themes.getColorAccent(launcher); 205 mGradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, 206 Themes.getAttrBoolean(launcher, R.attr.isMainColorDark) 207 ? new int[] {0xB3FFFFFF, 0x00FFFFFF} 208 : new int[] {ColorUtils.setAlphaComponent(accentColor, 127), 209 ColorUtils.setAlphaComponent(accentColor, 0)}); 210 float r = mWidthPx / 2f; 211 mGradient.setCornerRadii(new float[] {r, r, r, r, 0, 0, 0, 0}); 212 213 int top = mMaxHeightPx - mCircleSizePx + mPaddingPx; 214 mCircle.setBounds(mPaddingPx, top, mPaddingPx + mCircleSizePx, top + mCircleSizePx); 215 mGradient.setBounds(0, mMaxHeightPx - mCircleSizePx, mWidthPx, mMaxHeightPx); 216 217 DeviceProfile grid = launcher.getDeviceProfile(); 218 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(mWidthPx, mMaxHeightPx); 219 lp.ignoreInsets = true; 220 lp.leftMargin = (grid.widthPx - mWidthPx) / 2; 221 lp.topMargin = grid.heightPx - grid.hotseatBarSizePx - mMaxHeightPx; 222 setLayoutParams(lp); 223 } 224 225 /** 226 * Shows the All Apps education view and plays the animation. 227 */ show(Launcher launcher)228 public static void show(Launcher launcher) { 229 final DragLayer dragLayer = launcher.getDragLayer(); 230 ViewGroup parent = (ViewGroup) dragLayer.getParent(); 231 AllAppsEduView view = launcher.getViewCache().getView(R.layout.all_apps_edu_view, 232 launcher, parent); 233 view.init(launcher); 234 launcher.getDragLayer().addView(view); 235 launcher.getStatsLogManager().logger().log(LAUNCHER_ALL_APPS_EDU_SHOWN); 236 237 view.requestLayout(); 238 view.playAnimation(); 239 } 240 } 241