1 /*
2  * Copyright (C) 2018 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_HEADER_EXTRA;
19 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
20 import static com.android.launcher3.LauncherState.OVERVIEW;
21 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
22 import static com.android.launcher3.anim.Interpolators.ACCEL;
23 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
24 import static com.android.launcher3.anim.Interpolators.LINEAR;
25 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
26 import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
27 
28 import android.content.Context;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.Paint;
32 import android.graphics.Path;
33 import android.graphics.Path.Direction;
34 import android.graphics.Path.Op;
35 import android.graphics.Rect;
36 import android.util.AttributeSet;
37 import android.view.animation.Interpolator;
38 
39 import com.android.launcher3.BaseQuickstepLauncher;
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.LauncherState;
42 import com.android.launcher3.R;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.anim.Interpolators;
45 import com.android.launcher3.config.FeatureFlags;
46 import com.android.launcher3.uioverrides.states.OverviewState;
47 import com.android.launcher3.util.OnboardingPrefs;
48 import com.android.launcher3.util.Themes;
49 import com.android.launcher3.views.ScrimView;
50 import com.android.quickstep.SysUINavigationMode;
51 import com.android.quickstep.SysUINavigationMode.Mode;
52 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
53 import com.android.quickstep.util.LayoutUtils;
54 
55 /**
56  * Scrim used for all-apps and shelf in Overview
57  * In transposed layout, it behaves as a simple color scrim.
58  * In portrait layout, it draws a rounded rect such that
59  *    From normal state to overview state, the shelf just fades in and does not move
60  *    From overview state to all-apps state the shelf moves up and fades in to cover the screen
61  */
62 public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
63         implements NavigationModeChangeListener {
64 
65     // If the progress is more than this, shelf follows the finger, otherwise it moves faster to
66     // cover the whole screen
67     private static final float SCRIM_CATCHUP_THRESHOLD = 0.2f;
68 
69     // Temporarily needed until android.R.attr.bottomDialogCornerRadius becomes public
70     private static final float BOTTOM_CORNER_RADIUS_RATIO = 2f;
71 
72     // In transposed layout, we simply draw a flat color.
73     private boolean mDrawingFlatColor;
74 
75     // For shelf mode
76     private final int mEndAlpha;
77     private final float mRadius;
78     private final int mMaxScrimAlpha;
79     private final Paint mPaint;
80     private final OnboardingPrefs mOnboardingPrefs;
81 
82     // Mid point where the alpha changes
83     private int mMidAlpha;
84     private float mMidProgress;
85 
86     // The progress at which the drag handle starts moving up with the shelf.
87     private float mDragHandleProgress;
88 
89     private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
90     private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
91 
92     private float mShiftRange;
93 
94     private float mTopOffset;
95     private float mShelfTop;
96     private float mShelfTopAtThreshold;
97 
98     private int mShelfColor;
99     private int mRemainingScreenColor;
100 
101     private final Path mTempPath = new Path();
102     private final Path mRemainingScreenPath = new Path();
103     private boolean mRemainingScreenPathValid = false;
104 
105     private Mode mSysUINavigationMode;
106     private boolean mIsTwoZoneSwipeModel;
107 
ShelfScrimView(Context context, AttributeSet attrs)108     public ShelfScrimView(Context context, AttributeSet attrs) {
109         super(context, attrs);
110         mMaxScrimAlpha = Math.round(OVERVIEW.getOverviewScrimAlpha(mLauncher) * 255);
111 
112         mEndAlpha = Color.alpha(mEndScrim);
113         mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
114         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
115         mOnboardingPrefs = mLauncher.getOnboardingPrefs();
116 
117         // Just assume the easiest UI for now, until we have the proper layout information.
118         mDrawingFlatColor = true;
119     }
120 
121     @Override
onSizeChanged(int w, int h, int oldw, int oldh)122     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
123         super.onSizeChanged(w, h, oldw, oldh);
124         mRemainingScreenPathValid = false;
125     }
126 
127     @Override
onAttachedToWindow()128     protected void onAttachedToWindow() {
129         super.onAttachedToWindow();
130         onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(getContext())
131                 .addModeChangeListener(this));
132     }
133 
134     @Override
onDetachedFromWindow()135     protected void onDetachedFromWindow() {
136         super.onDetachedFromWindow();
137         SysUINavigationMode.INSTANCE.get(getContext()).removeModeChangeListener(this);
138     }
139 
140     @Override
onNavigationModeChanged(Mode newMode)141     public void onNavigationModeChanged(Mode newMode) {
142         mSysUINavigationMode = newMode;
143         // Note that these interpolators are inverted because progress goes 1 to 0.
144         if (mSysUINavigationMode == Mode.NO_BUTTON) {
145             // Show the shelf more quickly before reaching overview progress.
146             mBeforeMidProgressColorInterpolator = ACCEL_2;
147             mAfterMidProgressColorInterpolator = ACCEL;
148             mIsTwoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get();
149         } else {
150             mBeforeMidProgressColorInterpolator = ACCEL;
151             mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
152             mIsTwoZoneSwipeModel = false;
153         }
154     }
155 
156     @Override
reInitUi()157     public void reInitUi() {
158         DeviceProfile dp = mLauncher.getDeviceProfile();
159         mDrawingFlatColor = dp.isVerticalBarLayout();
160 
161         if (!mDrawingFlatColor) {
162             mRemainingScreenPathValid = false;
163             mShiftRange = mLauncher.getAllAppsController().getShiftRange();
164 
165             Context context = getContext();
166             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
167                 mDragHandleProgress = 1;
168                 if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
169                         && SysUINavigationMode.removeShelfFromOverview(context)) {
170                     // Fade in all apps background quickly to distinguish from swiping from nav bar.
171                     mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
172                     mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
173                 } else {
174                     mMidAlpha = 0;
175                     mMidProgress = 1;
176                 }
177             } else {
178                 mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
179                 mMidProgress =  OVERVIEW.getVerticalProgress(mLauncher);
180                 Rect hotseatPadding = dp.getHotseatLayoutPadding();
181                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
182                         + hotseatPadding.bottom + hotseatPadding.top;
183                 float dragHandleTop =
184                         Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
185                 mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
186             }
187             mTopOffset = dp.getInsets().top - mDragHandleSize.y;
188             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
189         }
190         updateColors();
191         updateSysUiColors();
192         updateDragHandleAlpha();
193         invalidate();
194     }
195 
196     @Override
updateColors()197     public void updateColors() {
198         super.updateColors();
199         mDragHandleOffset = 0;
200         if (mDrawingFlatColor) {
201             return;
202         }
203 
204         if (mProgress < mDragHandleProgress) {
205             mDragHandleOffset = mShiftRange * (mDragHandleProgress - mProgress);
206         }
207 
208         if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
209             mShelfTop = mShiftRange * mProgress + mTopOffset;
210         } else {
211             mShelfTop = Utilities.mapRange(mProgress / SCRIM_CATCHUP_THRESHOLD, -mRadius,
212                     mShelfTopAtThreshold);
213         }
214 
215         if (mProgress >= 1) {
216             mRemainingScreenColor = 0;
217             mShelfColor = 0;
218             LauncherState state = mLauncher.getStateManager().getState();
219             if (mSysUINavigationMode == Mode.NO_BUTTON
220                     && (state == BACKGROUND_APP || state == QUICK_SWITCH)
221                     && mLauncher.getShelfPeekAnim().isPeeking()) {
222                 // Show the shelf background when peeking during swipe up.
223                 mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
224             }
225         } else if (mProgress >= mMidProgress) {
226             mRemainingScreenColor = 0;
227 
228             int alpha = Math.round(Utilities.mapToRange(
229                     mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
230             mShelfColor = setColorAlphaBound(mEndScrim, alpha);
231         } else {
232             // Note that these ranges and interpolators are inverted because progress goes 1 to 0.
233             int alpha = Math.round(
234                     Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
235                             (float) mMidAlpha, mAfterMidProgressColorInterpolator));
236             mShelfColor = setColorAlphaBound(mEndScrim, alpha);
237 
238             int remainingScrimAlpha = Math.round(
239                     Utilities.mapToRange(mProgress, (float) 0, mMidProgress, mMaxScrimAlpha,
240                             (float) 0, LINEAR));
241             mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
242         }
243     }
244 
245     @Override
updateSysUiColors()246     protected void updateSysUiColors() {
247         if (mDrawingFlatColor) {
248             super.updateSysUiColors();
249         } else {
250             // Use a light system UI (dark icons) if all apps is behind at least half of the
251             // status bar.
252             boolean forceChange = mShelfTop <= mLauncher.getDeviceProfile().getInsets().top / 2f;
253             if (forceChange) {
254                 mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
255             } else {
256                 mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
257             }
258         }
259     }
260 
261     @Override
shouldDragHandleBeVisible()262     protected boolean shouldDragHandleBeVisible() {
263         boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
264                 && !mOnboardingPrefs.hasReachedMaxCount(OnboardingPrefs.ALL_APPS_COUNT);
265         return needsAllAppsEdu || super.shouldDragHandleBeVisible();
266     }
267 
268     @Override
onDraw(Canvas canvas)269     protected void onDraw(Canvas canvas) {
270         drawBackground(canvas);
271         drawDragHandle(canvas);
272     }
273 
drawBackground(Canvas canvas)274     private void drawBackground(Canvas canvas) {
275         if (mDrawingFlatColor) {
276             if (mCurrentFlatColor != 0) {
277                 canvas.drawColor(mCurrentFlatColor);
278             }
279             return;
280         }
281 
282         if (Color.alpha(mShelfColor) == 0) {
283             return;
284         } else if (mProgress <= 0) {
285             canvas.drawColor(mShelfColor);
286             return;
287         }
288 
289         int height = getHeight();
290         int width = getWidth();
291         // Draw the scrim over the remaining screen if needed.
292         if (mRemainingScreenColor != 0) {
293             if (!mRemainingScreenPathValid) {
294                 mTempPath.reset();
295                 // Using a arbitrary '+10' in the bottom to avoid any left-overs at the
296                 // corners due to rounding issues.
297                 mTempPath.addRoundRect(0, height - mRadius, width, height + mRadius + 10,
298                         mRadius, mRadius, Direction.CW);
299                 mRemainingScreenPath.reset();
300                 mRemainingScreenPath.addRect(0, 0, width, height, Direction.CW);
301                 mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE);
302             }
303 
304             float offset = height - mRadius - mShelfTop;
305             canvas.translate(0, -offset);
306             mPaint.setColor(mRemainingScreenColor);
307             canvas.drawPath(mRemainingScreenPath, mPaint);
308             canvas.translate(0, offset);
309         }
310 
311         mPaint.setColor(mShelfColor);
312         canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
313     }
314 
315     @Override
getVisualTop()316     public float getVisualTop() {
317         return mShelfTop;
318     }
319 }
320