1 /* 2 * Copyright (C) 2017 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 17 package com.android.quickstep.views; 18 19 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; 20 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 21 22 import static com.android.systemui.shared.recents.utilities.PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT; 23 import static com.android.systemui.shared.recents.utilities.Utilities.isRelativePercentDifferenceGreaterThan; 24 25 import android.content.Context; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapShader; 28 import android.graphics.Canvas; 29 import android.graphics.Color; 30 import android.graphics.ColorFilter; 31 import android.graphics.Insets; 32 import android.graphics.Matrix; 33 import android.graphics.Paint; 34 import android.graphics.PorterDuff; 35 import android.graphics.PorterDuffXfermode; 36 import android.graphics.Rect; 37 import android.graphics.RectF; 38 import android.graphics.Shader; 39 import android.graphics.drawable.Drawable; 40 import android.os.Build; 41 import android.util.AttributeSet; 42 import android.util.FloatProperty; 43 import android.util.Property; 44 import android.view.View; 45 import android.widget.ImageView; 46 47 import androidx.annotation.Nullable; 48 import androidx.annotation.RequiresApi; 49 import androidx.core.graphics.ColorUtils; 50 51 import com.android.launcher3.DeviceProfile; 52 import com.android.launcher3.Utilities; 53 import com.android.launcher3.util.MainThreadInitializedObject; 54 import com.android.launcher3.util.SystemUiController; 55 import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags; 56 import com.android.launcher3.util.ViewPool; 57 import com.android.quickstep.TaskOverlayFactory.TaskOverlay; 58 import com.android.quickstep.orientation.RecentsPagedOrientationHandler; 59 import com.android.quickstep.views.TaskView.FullscreenDrawParams; 60 import com.android.systemui.shared.recents.model.Task; 61 import com.android.systemui.shared.recents.model.ThumbnailData; 62 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper; 63 64 import java.util.Objects; 65 66 /** 67 * A task in the Recents view. 68 * 69 * @deprecated This class will be replaced by the new [TaskThumbnailView]. 70 */ 71 @Deprecated 72 public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable { 73 private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS = 74 new MainThreadInitializedObject<>(FullscreenDrawParams::new); 75 76 public static final Property<TaskThumbnailViewDeprecated, Float> DIM_ALPHA = 77 new FloatProperty<TaskThumbnailViewDeprecated>("dimAlpha") { 78 @Override 79 public void setValue(TaskThumbnailViewDeprecated thumbnail, float dimAlpha) { 80 thumbnail.setDimAlpha(dimAlpha); 81 } 82 83 @Override 84 public Float get(TaskThumbnailViewDeprecated thumbnailView) { 85 return thumbnailView.mDimAlpha; 86 } 87 }; 88 89 public static final Property<TaskThumbnailViewDeprecated, Float> SPLASH_ALPHA = 90 new FloatProperty<TaskThumbnailViewDeprecated>("splashAlpha") { 91 @Override 92 public void setValue(TaskThumbnailViewDeprecated thumbnail, float splashAlpha) { 93 thumbnail.setSplashAlpha(splashAlpha); 94 } 95 96 @Override 97 public Float get(TaskThumbnailViewDeprecated thumbnailView) { 98 return thumbnailView.mSplashAlpha / 255f; 99 } 100 }; 101 102 /** Use to animate thumbnail translationX while first app in split selection is initiated */ 103 public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_X = 104 new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateX") { 105 @Override 106 public void setValue(TaskThumbnailViewDeprecated thumbnail, 107 float splitSelectTranslateX) { 108 thumbnail.applySplitSelectTranslateX(splitSelectTranslateX); 109 } 110 111 @Override 112 public Float get(TaskThumbnailViewDeprecated thumbnailView) { 113 return thumbnailView.mSplitSelectTranslateX; 114 } 115 }; 116 117 /** Use to animate thumbnail translationY while first app in split selection is initiated */ 118 public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_Y = 119 new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateY") { 120 @Override 121 public void setValue(TaskThumbnailViewDeprecated thumbnail, 122 float splitSelectTranslateY) { 123 thumbnail.applySplitSelectTranslateY(splitSelectTranslateY); 124 } 125 126 @Override 127 public Float get(TaskThumbnailViewDeprecated thumbnailView) { 128 return thumbnailView.mSplitSelectTranslateY; 129 } 130 }; 131 132 private final RecentsViewContainer mContainer; 133 private TaskOverlay<?> mOverlay; 134 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 135 private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 136 private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 137 private final Paint mClearPaint = new Paint(); 138 private final Paint mDimmingPaintAfterClearing = new Paint(); 139 private final int mDimColor; 140 141 // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0. 142 private final Rect mPreviewRect = new Rect(); 143 private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper(); 144 private TaskView.FullscreenDrawParams mFullscreenParams; 145 private ImageView mSplashView; 146 private Drawable mSplashViewDrawable; 147 148 @Nullable 149 private Task mTask; 150 @Nullable 151 private ThumbnailData mThumbnailData; 152 @Nullable 153 protected BitmapShader mBitmapShader; 154 155 /** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */ 156 private float mDimAlpha = 0f; 157 /** Controls visibility of the splash view, 0 is transparent, 255 fully opaque. */ 158 private int mSplashAlpha = 0; 159 160 private boolean mOverlayEnabled; 161 /** Used as a placeholder when the original thumbnail animates out to. */ 162 private boolean mShowSplashForSplitSelection; 163 private float mSplitSelectTranslateX; 164 private float mSplitSelectTranslateY; 165 TaskThumbnailViewDeprecated(Context context)166 public TaskThumbnailViewDeprecated(Context context) { 167 this(context, null); 168 } 169 TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs)170 public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs) { 171 this(context, attrs, 0); 172 } 173 TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs, int defStyleAttr)174 public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs, 175 int defStyleAttr) { 176 super(context, attrs, defStyleAttr); 177 mPaint.setFilterBitmap(true); 178 mBackgroundPaint.setColor(Color.WHITE); 179 mSplashBackgroundPaint.setColor(Color.WHITE); 180 mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 181 mContainer = RecentsViewContainer.containerFromContext(context); 182 // Initialize with placeholder value. It is overridden later by TaskView 183 mFullscreenParams = TEMP_PARAMS.get(context); 184 185 mDimColor = RecentsView.getForegroundScrimDimColor(context); 186 mDimmingPaintAfterClearing.setColor(mDimColor); 187 } 188 189 /** 190 * Updates the thumbnail to draw the provided task 191 */ bind(Task task, TaskOverlay<?> overlay)192 public void bind(Task task, TaskOverlay<?> overlay) { 193 mOverlay = overlay; 194 mOverlay.reset(); 195 mTask = task; 196 int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000; 197 mPaint.setColor(color); 198 mBackgroundPaint.setColor(color); 199 mSplashBackgroundPaint.setColor(color); 200 updateSplashView(mTask.icon); 201 } 202 203 /** 204 * Sets TaskOverlay without binding a task. 205 * 206 * @deprecated Should only be used when using new 207 * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}. 208 */ 209 @Deprecated setTaskOverlay(TaskOverlay<?> overlay)210 public void setTaskOverlay(TaskOverlay<?> overlay) { 211 mOverlay = overlay; 212 } 213 214 /** 215 * Updates the thumbnail. 216 * 217 * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately. 218 * In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)} 219 * version with {@code refreshNow} is true. The only exception is 220 * in the live tile case that we grab a screenshot when user enters Overview 221 * upon swipe up so that a usable screenshot is accessible immediately when 222 * recents animation needs to be finished / cancelled. 223 */ setThumbnail(@ullable Task task, @Nullable ThumbnailData thumbnailData, boolean refreshNow)224 public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData, 225 boolean refreshNow) { 226 mTask = task; 227 ThumbnailData oldThumbnailData = mThumbnailData; 228 mThumbnailData = (thumbnailData != null && thumbnailData.getThumbnail() != null) 229 ? thumbnailData : null; 230 if (mTask != null) { 231 updateSplashView(mTask.icon); 232 } 233 if (refreshNow) { 234 Long oldSnapshotId = oldThumbnailData != null ? oldThumbnailData.getSnapshotId() : null; 235 Long snapshotId = mThumbnailData != null ? mThumbnailData.getSnapshotId() : null; 236 refresh(snapshotId != null && !Objects.equals(oldSnapshotId, snapshotId)); 237 } 238 } 239 240 /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */ setThumbnail(@ullable Task task, @Nullable ThumbnailData thumbnailData)241 public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData) { 242 setThumbnail(task, thumbnailData, true /* refreshNow */); 243 } 244 245 /** Updates the shader, paint, matrix to redraw. */ refresh()246 public void refresh() { 247 refresh(false); 248 } 249 250 /** 251 * Updates the shader, paint, matrix to redraw. 252 * 253 * @param shouldRefreshOverlay whether to re-initialize overlay 254 */ refresh(boolean shouldRefreshOverlay)255 private void refresh(boolean shouldRefreshOverlay) { 256 if (mThumbnailData != null && mThumbnailData.getThumbnail() != null) { 257 Bitmap bm = mThumbnailData.getThumbnail(); 258 bm.prepareToDraw(); 259 mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 260 mPaint.setShader(mBitmapShader); 261 updateThumbnailMatrix(); 262 if (shouldRefreshOverlay) { 263 refreshOverlay(); 264 } 265 } else { 266 mBitmapShader = null; 267 mThumbnailData = null; 268 mPaint.setShader(null); 269 mOverlay.reset(); 270 } 271 updateThumbnailPaintFilter(); 272 } 273 274 /** 275 * Sets the alpha of the dim layer on top of this view. 276 * <p> 277 * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the 278 * extracted background color. 279 */ setDimAlpha(float dimAlpha)280 public void setDimAlpha(float dimAlpha) { 281 mDimAlpha = dimAlpha; 282 updateThumbnailPaintFilter(); 283 } 284 285 /** 286 * Sets the alpha of the splash view. 287 */ setSplashAlpha(float splashAlpha)288 public void setSplashAlpha(float splashAlpha) { 289 mSplashAlpha = (int) (Utilities.boundToRange(splashAlpha, 0f, 1f) * 255); 290 if (mSplashViewDrawable != null) { 291 mSplashViewDrawable.setAlpha(mSplashAlpha); 292 } 293 mSplashBackgroundPaint.setAlpha(mSplashAlpha); 294 invalidate(); 295 } 296 getDimAlpha()297 public float getDimAlpha() { 298 return mDimAlpha; 299 } 300 301 /** 302 * Get the scaled insets that are being used to draw the task view. This is a subsection of 303 * the full snapshot. 304 * 305 * @return the insets in snapshot bitmap coordinates. 306 */ 307 @RequiresApi(api = Build.VERSION_CODES.Q) getScaledInsets()308 public Insets getScaledInsets() { 309 if (mThumbnailData == null) { 310 return Insets.NONE; 311 } 312 313 RectF bitmapRect = new RectF( 314 0, 315 0, 316 mThumbnailData.getThumbnail().getWidth(), 317 mThumbnailData.getThumbnail().getHeight()); 318 RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()); 319 320 // The position helper matrix tells us how to transform the bitmap to fit the view, the 321 // inverse tells us where the view would be in the bitmaps coordinates. The insets are the 322 // difference between the bitmap bounds and the projected view bounds. 323 Matrix boundsToBitmapSpace = new Matrix(); 324 mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace); 325 RectF boundsInBitmapSpace = new RectF(); 326 boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect); 327 328 DeviceProfile dp = mContainer.getDeviceProfile(); 329 int bottomInset = dp.isTablet 330 ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0; 331 return Insets.of(0, 0, 0, bottomInset); 332 } 333 334 335 @SystemUiControllerFlags getSysUiStatusNavFlags()336 public int getSysUiStatusNavFlags() { 337 if (mThumbnailData != null) { 338 int flags = 0; 339 flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_STATUS_BARS) != 0 340 ? SystemUiController.FLAG_LIGHT_STATUS 341 : SystemUiController.FLAG_DARK_STATUS; 342 flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0 343 ? SystemUiController.FLAG_LIGHT_NAV 344 : SystemUiController.FLAG_DARK_NAV; 345 return flags; 346 } 347 return 0; 348 } 349 350 @Override onLayout(boolean changed, int left, int top, int right, int bottom)351 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 352 super.onLayout(changed, left, top, right, bottom); 353 updateSplashView(mSplashViewDrawable); 354 } 355 356 @Override onDraw(Canvas canvas)357 protected void onDraw(Canvas canvas) { 358 canvas.save(); 359 // Draw the insets if we're being drawn fullscreen (we do this for quick switch). 360 drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), 361 mFullscreenParams.getCurrentDrawnCornerRadius()); 362 canvas.restore(); 363 } 364 getPreviewPositionHelper()365 public PreviewPositionHelper getPreviewPositionHelper() { 366 return mPreviewPositionHelper; 367 } 368 setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)369 public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) { 370 mFullscreenParams = fullscreenParams; 371 invalidate(); 372 } 373 drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius)374 public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height, 375 float cornerRadius) { 376 if (mTask != null && getTaskView().isRunningTask() 377 && !getTaskView().getShouldShowScreenshot()) { 378 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint); 379 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, 380 mDimmingPaintAfterClearing); 381 return; 382 } 383 384 // Always draw the background since the snapshots might be translucent or partially empty 385 // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss 386 // split screen). 387 canvas.drawRoundRect(x, y + 1, width, height - 1, cornerRadius, 388 cornerRadius, mBackgroundPaint); 389 390 final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null 391 || mThumbnailData == null; 392 if (drawBackgroundOnly) { 393 return; 394 } 395 396 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint); 397 398 // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios. 399 if (shouldShowSplashView()) { 400 float cornerRadiusX = cornerRadius; 401 float cornerRadiusY = cornerRadius; 402 if (mShowSplashForSplitSelection) { 403 cornerRadiusX = cornerRadius / getScaleX(); 404 cornerRadiusY = cornerRadius / getScaleY(); 405 } 406 407 // Always draw background for hiding inconsistencies, even if splash view is not yet 408 // loaded (which can happen as task icons are loaded asynchronously in the background) 409 canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX, 410 cornerRadiusY, mSplashBackgroundPaint); 411 if (mSplashView != null) { 412 mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1); 413 mSplashView.draw(canvas); 414 } 415 } 416 } 417 418 /** See {@link #SPLIT_SELECT_TRANSLATE_X} */ applySplitSelectTranslateX(float splitSelectTranslateX)419 protected void applySplitSelectTranslateX(float splitSelectTranslateX) { 420 mSplitSelectTranslateX = splitSelectTranslateX; 421 applyTranslateX(); 422 } 423 424 /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */ applySplitSelectTranslateY(float splitSelectTranslateY)425 protected void applySplitSelectTranslateY(float splitSelectTranslateY) { 426 mSplitSelectTranslateY = splitSelectTranslateY; 427 applyTranslateY(); 428 } 429 applyTranslateX()430 private void applyTranslateX() { 431 setTranslationX(mSplitSelectTranslateX); 432 } 433 applyTranslateY()434 private void applyTranslateY() { 435 setTranslationY(mSplitSelectTranslateY); 436 } 437 resetViewTransforms()438 protected void resetViewTransforms() { 439 mSplitSelectTranslateX = 0; 440 mSplitSelectTranslateY = 0; 441 } 442 getTaskView()443 public TaskView getTaskView() { 444 return (TaskView) getParent(); 445 } 446 setOverlayEnabled(boolean overlayEnabled)447 public void setOverlayEnabled(boolean overlayEnabled) { 448 if (mOverlayEnabled != overlayEnabled) { 449 mOverlayEnabled = overlayEnabled; 450 451 refreshOverlay(); 452 } 453 } 454 455 /** 456 * Determine if the splash should be shown over top of the thumbnail. 457 * 458 * <p>We want to show the splash if the aspect ratio or rotation of the thumbnail would be 459 * different from the task. 460 */ shouldShowSplashView()461 public boolean shouldShowSplashView() { 462 return isThumbnailAspectRatioDifferentFromThumbnailData() 463 || isThumbnailRotationDifferentFromTask() 464 || mShowSplashForSplitSelection; 465 } 466 setShowSplashForSplitSelection(boolean showSplashForSplitSelection)467 public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) { 468 mShowSplashForSplitSelection = showSplashForSplitSelection; 469 } 470 refreshSplashView()471 protected void refreshSplashView() { 472 if (mTask != null) { 473 updateSplashView(mTask.icon); 474 invalidate(); 475 } 476 } 477 updateSplashView(Drawable icon)478 private void updateSplashView(Drawable icon) { 479 if (icon == null || icon.getConstantState() == null) { 480 mSplashViewDrawable = null; 481 mSplashView = null; 482 return; 483 } 484 mSplashViewDrawable = icon.getConstantState().newDrawable().mutate(); 485 mSplashViewDrawable.setAlpha(mSplashAlpha); 486 ImageView imageView = mSplashView == null ? new ImageView(getContext()) : mSplashView; 487 imageView.setImageDrawable(mSplashViewDrawable); 488 489 imageView.setScaleType(ImageView.ScaleType.MATRIX); 490 Matrix matrix = new Matrix(); 491 float drawableWidth = mSplashViewDrawable.getIntrinsicWidth(); 492 float drawableHeight = mSplashViewDrawable.getIntrinsicHeight(); 493 float viewWidth = getMeasuredWidth(); 494 float viewCenterX = viewWidth / 2f; 495 float viewHeight = getMeasuredHeight(); 496 float viewCenterY = viewHeight / 2f; 497 float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f; 498 float centeredDrawableTop = (viewHeight - drawableHeight) / 2f; 499 float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale(); 500 float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null 501 ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen(); 502 float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX()); 503 float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY()); 504 505 // Center the image in the view. 506 matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop); 507 // Apply scale transformation after translation, pivoting around center of view. 508 matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY); 509 510 imageView.setImageMatrix(matrix); 511 mSplashView = imageView; 512 } 513 isThumbnailAspectRatioDifferentFromThumbnailData()514 private boolean isThumbnailAspectRatioDifferentFromThumbnailData() { 515 if (mThumbnailData == null || mThumbnailData.getThumbnail() == null) { 516 return false; 517 } 518 519 float thumbnailViewAspect = getWidth() / (float) getHeight(); 520 float thumbnailDataAspect = mThumbnailData.getThumbnail().getWidth() 521 / (float) mThumbnailData.getThumbnail().getHeight(); 522 523 return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect, 524 thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); 525 } 526 isThumbnailRotationDifferentFromTask()527 private boolean isThumbnailRotationDifferentFromTask() { 528 RecentsView recents = getTaskView().getRecentsView(); 529 if (recents == null || mThumbnailData == null) { 530 return false; 531 } 532 533 if (recents.getPagedOrientationHandler() == RecentsPagedOrientationHandler.PORTRAIT) { 534 int currentRotation = recents.getPagedViewOrientedState().getRecentsActivityRotation(); 535 return (currentRotation - mThumbnailData.rotation) % 2 != 0; 536 } else { 537 return recents.getPagedOrientationHandler().getRotation() != mThumbnailData.rotation; 538 } 539 } 540 541 /** 542 * Potentially re-init the task overlay. Be cautious when calling this as the overlay may 543 * do processing on initialization. 544 */ refreshOverlay()545 private void refreshOverlay() { 546 if (mOverlayEnabled) { 547 mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(), 548 mPreviewPositionHelper.isOrientationChanged()); 549 } else { 550 mOverlay.reset(); 551 } 552 } 553 updateThumbnailPaintFilter()554 private void updateThumbnailPaintFilter() { 555 ColorFilter filter = getColorFilter(mDimAlpha); 556 mBackgroundPaint.setColorFilter(filter); 557 int alpha = (int) (mDimAlpha * 255); 558 mDimmingPaintAfterClearing.setAlpha(alpha); 559 if (mBitmapShader != null) { 560 mPaint.setColorFilter(filter); 561 } else { 562 mPaint.setColorFilter(null); 563 mPaint.setColor(ColorUtils.blendARGB(Color.BLACK, mDimColor, alpha)); 564 } 565 invalidate(); 566 } 567 updateThumbnailMatrix()568 private void updateThumbnailMatrix() { 569 DeviceProfile dp = mContainer.getDeviceProfile(); 570 mPreviewPositionHelper.setOrientationChanged(false); 571 if (mBitmapShader != null && mThumbnailData != null) { 572 mPreviewRect.set(0, 0, mThumbnailData.getThumbnail().getWidth(), 573 mThumbnailData.getThumbnail().getHeight()); 574 int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation(); 575 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 576 mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData, 577 getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl); 578 579 mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix()); 580 mPaint.setShader(mBitmapShader); 581 } 582 getTaskView().updateCurrentFullscreenParams(); 583 invalidate(); 584 } 585 586 @Override onSizeChanged(int w, int h, int oldw, int oldh)587 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 588 super.onSizeChanged(w, h, oldw, oldh); 589 updateThumbnailMatrix(); 590 591 refreshOverlay(); 592 } 593 getColorFilter(float dimAmount)594 private ColorFilter getColorFilter(float dimAmount) { 595 return Utilities.makeColorTintingColorFilter(mDimColor, dimAmount); 596 } 597 598 /** 599 * Returns current thumbnail or null if none is set. 600 */ 601 @Nullable getThumbnail()602 public Bitmap getThumbnail() { 603 if (mThumbnailData == null) { 604 return null; 605 } 606 return mThumbnailData.getThumbnail(); 607 } 608 609 /** 610 * Returns whether the snapshot is real. If the device is locked for the user of the task, 611 * the snapshot used will be an app-theme generated snapshot instead of a real snapshot. 612 */ isRealSnapshot()613 public boolean isRealSnapshot() { 614 if (mThumbnailData == null) { 615 return false; 616 } 617 return mThumbnailData.isRealSnapshot && !mTask.isLocked; 618 } 619 620 @Override onRecycle()621 public void onRecycle() { 622 // Do nothing 623 } 624 } 625