• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.animation.Interpolators.FAST_OUT_SLOW_IN;
19 import static com.android.app.animation.Interpolators.LINEAR;
20 import static com.android.app.animation.Interpolators.OVERSHOOT_1_7;
21 import static com.android.launcher3.LauncherState.ALL_APPS;
22 import static com.android.launcher3.LauncherState.NORMAL;
23 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN;
25 import static com.android.quickstep.util.AnimUtils.clampToDuration;
26 
27 import android.animation.Animator;
28 import android.animation.AnimatorListenerAdapter;
29 import android.animation.AnimatorSet;
30 import android.animation.ValueAnimator;
31 import android.content.Context;
32 import android.graphics.Canvas;
33 import android.graphics.Rect;
34 import android.graphics.drawable.GradientDrawable;
35 import android.util.AttributeSet;
36 import android.view.MotionEvent;
37 import android.view.View;
38 
39 import androidx.annotation.Nullable;
40 import androidx.core.graphics.ColorUtils;
41 
42 import com.android.launcher3.AbstractFloatingView;
43 import com.android.launcher3.DeviceProfile;
44 import com.android.launcher3.Launcher;
45 import com.android.launcher3.R;
46 import com.android.launcher3.Utilities;
47 import com.android.launcher3.anim.AnimatorPlaybackController;
48 import com.android.launcher3.dragndrop.DragLayer;
49 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
50 import com.android.launcher3.util.Themes;
51 import com.android.quickstep.util.MultiValueUpdateListener;
52 
53 /**
54  * View used to educate the user on how to access All Apps when in No Nav Button navigation mode.
55  * If the user drags on the view, the animation is overridden so the user can swipe to All Apps or
56  * Home.
57  */
58 public class AllAppsEduView extends AbstractFloatingView {
59 
60     private Launcher mLauncher;
61     private AllAppsEduTouchController mTouchController;
62 
63     @Nullable
64     private AnimatorSet mAnimation;
65 
66     private GradientDrawable mCircle;
67     private GradientDrawable mGradient;
68 
69     private int mCircleSizePx;
70     private int mPaddingPx;
71     private int mWidthPx;
72     private int mMaxHeightPx;
73 
74     private boolean mCanInterceptTouch;
75 
AllAppsEduView(Context context, AttributeSet attrs)76     public AllAppsEduView(Context context, AttributeSet attrs) {
77         super(context, attrs);
78         mCircle = (GradientDrawable) context.getDrawable(R.drawable.all_apps_edu_circle);
79         mCircleSizePx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_circle_size);
80         mPaddingPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_padding);
81         mWidthPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_width);
82         mMaxHeightPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_max_height);
83         setWillNotDraw(false);
84     }
85 
86     @Override
onDraw(Canvas canvas)87     protected void onDraw(Canvas canvas) {
88         super.onDraw(canvas);
89         mGradient.draw(canvas);
90         mCircle.draw(canvas);
91     }
92 
93     @Override
onAttachedToWindow()94     protected void onAttachedToWindow() {
95         super.onAttachedToWindow();
96         mIsOpen = true;
97     }
98 
99     @Override
onDetachedFromWindow()100     protected void onDetachedFromWindow() {
101         super.onDetachedFromWindow();
102         mIsOpen = false;
103     }
104 
105     @Override
handleClose(boolean animate)106     protected void handleClose(boolean animate) {
107         mLauncher.getDragLayer().removeView(this);
108     }
109 
110     @Override
isOfType(int type)111     protected boolean isOfType(int type) {
112         return (type & TYPE_ALL_APPS_EDU) != 0;
113     }
114 
115     @Override
canInterceptEventsInSystemGestureRegion()116     public boolean canInterceptEventsInSystemGestureRegion() {
117         return true;
118     }
119 
120 
shouldInterceptTouch(MotionEvent ev)121     private boolean shouldInterceptTouch(MotionEvent ev) {
122         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
123             mCanInterceptTouch = (ev.getEdgeFlags() & EDGE_NAV_BAR) == 0;
124         }
125         return mCanInterceptTouch;
126     }
127 
128     @Override
onControllerTouchEvent(MotionEvent ev)129     public boolean onControllerTouchEvent(MotionEvent ev) {
130         if (shouldInterceptTouch(ev)) {
131             mTouchController.onControllerTouchEvent(ev);
132             updateAnimationOnTouchEvent(ev);
133         }
134         return true;
135     }
136 
updateAnimationOnTouchEvent(MotionEvent ev)137     private void updateAnimationOnTouchEvent(MotionEvent ev) {
138         if (mAnimation == null) {
139             return;
140         }
141         switch (ev.getActionMasked()) {
142             case MotionEvent.ACTION_DOWN:
143                 mAnimation.pause();
144                 return;
145             case MotionEvent.ACTION_UP:
146             case MotionEvent.ACTION_CANCEL:
147                 mAnimation.resume();
148                 return;
149         }
150 
151         if (mTouchController.isDraggingOrSettling()) {
152             mAnimation = null;
153             handleClose(false);
154         }
155     }
156 
157     @Override
onControllerInterceptTouchEvent(MotionEvent ev)158     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
159         if (shouldInterceptTouch(ev)) {
160             mTouchController.onControllerInterceptTouchEvent(ev);
161             updateAnimationOnTouchEvent(ev);
162         }
163         return true;
164     }
165 
playAnimation()166     private void playAnimation() {
167         if (mAnimation != null) {
168             return;
169         }
170         mAnimation = new AnimatorSet();
171 
172         final Rect circleBoundsOg = new Rect(mCircle.getBounds());
173         final Rect gradientBoundsOg = new Rect(mGradient.getBounds());
174         final Rect temp = new Rect();
175         final float transY = mMaxHeightPx - mCircleSizePx - mPaddingPx;
176 
177         // 1st: Circle alpha/scale
178         int firstPart = 600;
179         // 2nd: Circle animates upwards, Gradient alpha fades in, Gradient grows, All Apps hint
180         int secondPart = 1200;
181         int introDuration = firstPart + secondPart;
182 
183         AnimatorPlaybackController stateAnimationController =
184                 mTouchController.initAllAppsAnimation();
185         float maxAllAppsProgress = 0.75f;
186 
187         ValueAnimator intro = ValueAnimator.ofFloat(0, 1f);
188         intro.setInterpolator(LINEAR);
189         intro.setDuration(introDuration);
190         intro.addUpdateListener((new MultiValueUpdateListener() {
191             FloatProp mCircleAlpha = new FloatProp(0, 255,
192                     clampToDuration(LINEAR, 0, firstPart, introDuration));
193             FloatProp mCircleScale = new FloatProp(2f, 1f,
194                     clampToDuration(OVERSHOOT_1_7, 0, firstPart, introDuration));
195             FloatProp mDeltaY = new FloatProp(0, transY,
196                     clampToDuration(FAST_OUT_SLOW_IN, firstPart, secondPart, introDuration));
197             FloatProp mGradientAlpha = new FloatProp(0, 255,
198                     clampToDuration(LINEAR, firstPart, secondPart * 0.3f, introDuration));
199 
200             @Override
201             public void onUpdate(float progress, boolean initOnly) {
202                 temp.set(circleBoundsOg);
203                 temp.offset(0, (int) -mDeltaY.value);
204                 Utilities.scaleRectAboutCenter(temp, mCircleScale.value);
205                 mCircle.setBounds(temp);
206                 mCircle.setAlpha((int) mCircleAlpha.value);
207                 mGradient.setAlpha((int) mGradientAlpha.value);
208 
209                 temp.set(gradientBoundsOg);
210                 temp.top -= mDeltaY.value;
211                 mGradient.setBounds(temp);
212                 invalidate();
213 
214                 float stateProgress = Utilities.mapToRange(mDeltaY.value, 0, transY, 0,
215                         maxAllAppsProgress, LINEAR);
216                 stateAnimationController.setPlayFraction(stateProgress);
217             }
218         }));
219         intro.addListener(new AnimatorListenerAdapter() {
220             @Override
221             public void onAnimationEnd(Animator animation) {
222                 mCircle.setAlpha(0);
223                 mGradient.setAlpha(0);
224             }
225         });
226         mLauncher.getAppsView().setVisibility(View.VISIBLE);
227         mAnimation.play(intro);
228 
229         ValueAnimator closeAllApps = ValueAnimator.ofFloat(maxAllAppsProgress, 0f);
230         closeAllApps.addUpdateListener(valueAnimator -> {
231             stateAnimationController.setPlayFraction((float) valueAnimator.getAnimatedValue());
232         });
233         closeAllApps.setInterpolator(FAST_OUT_SLOW_IN);
234         closeAllApps.setStartDelay(introDuration);
235         closeAllApps.setDuration(250);
236         mAnimation.play(closeAllApps);
237 
238         mAnimation.addListener(new AnimatorListenerAdapter() {
239             @Override
240             public void onAnimationEnd(Animator animation) {
241                 mAnimation = null;
242                 // Handles cancelling the animation used to hint towards All Apps.
243                 mLauncher.getStateManager().goToState(NORMAL, false);
244                 handleClose(false);
245             }
246         });
247         mAnimation.start();
248     }
249 
init(Launcher launcher)250     private void init(Launcher launcher) {
251         mLauncher = launcher;
252         mTouchController = new AllAppsEduTouchController(mLauncher);
253 
254         int accentColor = Themes.getColorAccent(launcher);
255         mGradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
256                 Themes.getAttrBoolean(launcher, R.attr.isMainColorDark)
257                         ? new int[]{0xB3FFFFFF, 0x00FFFFFF}
258                         : new int[]{ColorUtils.setAlphaComponent(accentColor, 127),
259                                 ColorUtils.setAlphaComponent(accentColor, 0)});
260         float r = mWidthPx / 2f;
261         mGradient.setCornerRadii(new float[]{r, r, r, r, 0, 0, 0, 0});
262 
263         int top = mMaxHeightPx - mCircleSizePx + mPaddingPx;
264         mCircle.setBounds(mPaddingPx, top, mPaddingPx + mCircleSizePx, top + mCircleSizePx);
265         mGradient.setBounds(0, mMaxHeightPx - mCircleSizePx, mWidthPx, mMaxHeightPx);
266 
267         DeviceProfile grid = launcher.getDeviceProfile();
268         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(mWidthPx, mMaxHeightPx);
269         lp.ignoreInsets = true;
270         lp.leftMargin = (grid.widthPx - mWidthPx) / 2;
271         lp.topMargin = grid.heightPx - grid.hotseatBarSizePx - mMaxHeightPx;
272         setLayoutParams(lp);
273     }
274 
275     /**
276      * Shows the All Apps education view and plays the animation.
277      */
show(Launcher launcher)278     public static void show(Launcher launcher) {
279         final DragLayer dragLayer = launcher.getDragLayer();
280         AllAppsEduView view = (AllAppsEduView) launcher.getLayoutInflater().inflate(
281                 R.layout.all_apps_edu_view, dragLayer, false);
282         view.init(launcher);
283         launcher.getDragLayer().addView(view);
284         launcher.getStatsLogManager().logger().log(LAUNCHER_ALL_APPS_EDU_SHOWN);
285 
286         view.requestLayout();
287         view.playAnimation();
288     }
289 
290     private static class AllAppsEduTouchController extends PortraitStatesTouchController {
291 
AllAppsEduTouchController(Launcher l)292         private AllAppsEduTouchController(Launcher l) {
293             super(l);
294         }
295 
296         @Override
canInterceptTouch(MotionEvent ev)297         protected boolean canInterceptTouch(MotionEvent ev) {
298             return true;
299         }
300 
initAllAppsAnimation()301         private AnimatorPlaybackController initAllAppsAnimation() {
302             mFromState = NORMAL;
303             mToState = ALL_APPS;
304             mProgressMultiplier = initCurrentAnimation();
305             return mCurrentAnimation;
306         }
307 
isDraggingOrSettling()308         private boolean isDraggingOrSettling() {
309             return mDetector.isDraggingOrSettling();
310         }
311     }
312 }
313