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