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