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