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