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