* Strangely if you swipe up with a split pair and then tap on the live tile to go back to fullscreen this animates fairly correctly. However, if you drag the focused pair back to fullscreen the bottom app does not unclip as expected Bug: 219411750 Test: Swipe up w/ stashed taskbar in portrait. Thumbnail cutout doesn't appear when dragging to fullscreen Change-Id: Ia6e7aae10389d9fefa4adbfce540171bbe0a9180
521 lines
20 KiB
Java
521 lines
20 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.quickstep.views;
|
|
|
|
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
|
|
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
|
|
|
|
import static com.android.systemui.shared.recents.utilities.PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT;
|
|
import static com.android.systemui.shared.recents.utilities.Utilities.isRelativePercentDifferenceGreaterThan;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapShader;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.Insets;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Paint;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffXfermode;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Shader;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Build;
|
|
import android.util.AttributeSet;
|
|
import android.util.FloatProperty;
|
|
import android.util.Property;
|
|
import android.view.View;
|
|
import android.widget.ImageView;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.RequiresApi;
|
|
import androidx.core.graphics.ColorUtils;
|
|
|
|
import com.android.launcher3.BaseActivity;
|
|
import com.android.launcher3.DeviceProfile;
|
|
import com.android.launcher3.Utilities;
|
|
import com.android.launcher3.touch.PagedOrientationHandler;
|
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
|
import com.android.launcher3.util.SystemUiController;
|
|
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
|
|
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
|
|
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
|
|
import com.android.systemui.shared.recents.model.Task;
|
|
import com.android.systemui.shared.recents.model.ThumbnailData;
|
|
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
|
|
|
|
/**
|
|
* A task in the Recents view.
|
|
*/
|
|
public class TaskThumbnailView extends View {
|
|
private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
|
|
new MainThreadInitializedObject<>(FullscreenDrawParams::new);
|
|
|
|
public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
|
|
new FloatProperty<TaskThumbnailView>("dimAlpha") {
|
|
@Override
|
|
public void setValue(TaskThumbnailView thumbnail, float dimAlpha) {
|
|
thumbnail.setDimAlpha(dimAlpha);
|
|
}
|
|
|
|
@Override
|
|
public Float get(TaskThumbnailView thumbnailView) {
|
|
return thumbnailView.mDimAlpha;
|
|
}
|
|
};
|
|
|
|
private final BaseActivity mActivity;
|
|
@Nullable
|
|
private TaskOverlay mOverlay;
|
|
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private final Paint mClearPaint = new Paint();
|
|
private final Paint mDimmingPaintAfterClearing = new Paint();
|
|
private final int mDimColor;
|
|
|
|
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
|
|
private final Rect mPreviewRect = new Rect();
|
|
private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
|
|
private TaskView.FullscreenDrawParams mFullscreenParams;
|
|
private ImageView mSplashView;
|
|
private Drawable mSplashViewDrawable;
|
|
|
|
@Nullable
|
|
private Task mTask;
|
|
@Nullable
|
|
private ThumbnailData mThumbnailData;
|
|
@Nullable
|
|
protected BitmapShader mBitmapShader;
|
|
|
|
/** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */
|
|
private float mDimAlpha = 0f;
|
|
/** Controls visibility of the splash view, 0 is transparent, 255 fully opaque. */
|
|
private int mSplashAlpha = 0;
|
|
|
|
private boolean mOverlayEnabled;
|
|
|
|
public TaskThumbnailView(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public TaskThumbnailView(Context context, @Nullable AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public TaskThumbnailView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
super(context, attrs, defStyleAttr);
|
|
mPaint.setFilterBitmap(true);
|
|
mBackgroundPaint.setColor(Color.WHITE);
|
|
mSplashBackgroundPaint.setColor(Color.WHITE);
|
|
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
|
mActivity = BaseActivity.fromContext(context);
|
|
// Initialize with placeholder value. It is overridden later by TaskView
|
|
mFullscreenParams = TEMP_PARAMS.get(context);
|
|
|
|
mDimColor = RecentsView.getForegroundScrimDimColor(context);
|
|
mDimmingPaintAfterClearing.setColor(mDimColor);
|
|
}
|
|
|
|
/**
|
|
* Updates the thumbnail to draw the provided task
|
|
* @param task
|
|
*/
|
|
public void bind(Task task) {
|
|
getTaskOverlay().reset();
|
|
mTask = task;
|
|
int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
|
|
mPaint.setColor(color);
|
|
mBackgroundPaint.setColor(color);
|
|
mSplashBackgroundPaint.setColor(color);
|
|
updateSplashView(mTask.icon);
|
|
}
|
|
|
|
/**
|
|
* Updates the thumbnail.
|
|
* @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
|
|
* In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
|
|
* version with {@code refreshNow} is true. The only exception is
|
|
* in the live tile case that we grab a screenshot when user enters Overview
|
|
* upon swipe up so that a usable screenshot is accessible immediately when
|
|
* recents animation needs to be finished / cancelled.
|
|
*/
|
|
public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData,
|
|
boolean refreshNow) {
|
|
mTask = task;
|
|
boolean thumbnailWasNull = mThumbnailData == null;
|
|
mThumbnailData =
|
|
(thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
|
|
if (mTask != null) {
|
|
updateSplashView(mTask.icon);
|
|
}
|
|
if (refreshNow) {
|
|
refresh(thumbnailWasNull && mThumbnailData != null);
|
|
}
|
|
}
|
|
|
|
/** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
|
|
public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData) {
|
|
setThumbnail(task, thumbnailData, true /* refreshNow */);
|
|
}
|
|
|
|
/** Updates the shader, paint, matrix to redraw. */
|
|
public void refresh() {
|
|
refresh(false);
|
|
}
|
|
|
|
/**
|
|
* Updates the shader, paint, matrix to redraw.
|
|
* @param shouldRefreshOverlay whether to re-initialize overlay
|
|
*/
|
|
private void refresh(boolean shouldRefreshOverlay) {
|
|
if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
|
|
Bitmap bm = mThumbnailData.thumbnail;
|
|
bm.prepareToDraw();
|
|
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
|
mPaint.setShader(mBitmapShader);
|
|
updateThumbnailMatrix();
|
|
if (shouldRefreshOverlay) {
|
|
refreshOverlay();
|
|
}
|
|
} else {
|
|
mBitmapShader = null;
|
|
mThumbnailData = null;
|
|
mPaint.setShader(null);
|
|
getTaskOverlay().reset();
|
|
}
|
|
updateThumbnailPaintFilter();
|
|
}
|
|
|
|
/**
|
|
* Sets the alpha of the dim layer on top of this view.
|
|
* <p>
|
|
* If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the
|
|
* extracted background color.
|
|
*
|
|
*/
|
|
public void setDimAlpha(float dimAlpha) {
|
|
mDimAlpha = dimAlpha;
|
|
updateThumbnailPaintFilter();
|
|
}
|
|
|
|
/**
|
|
* Sets the alpha of the splash view.
|
|
*/
|
|
public void setSplashAlpha(float splashAlpha) {
|
|
mSplashAlpha = (int) (Utilities.boundToRange(splashAlpha, 0f, 1f) * 255);
|
|
if (mSplashViewDrawable != null) {
|
|
mSplashViewDrawable.setAlpha(mSplashAlpha);
|
|
}
|
|
mSplashBackgroundPaint.setAlpha(mSplashAlpha);
|
|
invalidate();
|
|
}
|
|
|
|
public TaskOverlay getTaskOverlay() {
|
|
if (mOverlay == null) {
|
|
mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
|
|
}
|
|
return mOverlay;
|
|
}
|
|
|
|
public float getDimAlpha() {
|
|
return mDimAlpha;
|
|
}
|
|
|
|
/**
|
|
* Get the scaled insets that are being used to draw the task view. This is a subsection of
|
|
* the full snapshot.
|
|
* @return the insets in snapshot bitmap coordinates.
|
|
*/
|
|
@RequiresApi(api = Build.VERSION_CODES.Q)
|
|
public Insets getScaledInsets() {
|
|
if (mThumbnailData == null) {
|
|
return Insets.NONE;
|
|
}
|
|
|
|
RectF bitmapRect = new RectF(
|
|
0, 0,
|
|
mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
|
|
RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
|
|
|
// The position helper matrix tells us how to transform the bitmap to fit the view, the
|
|
// inverse tells us where the view would be in the bitmaps coordinates. The insets are the
|
|
// difference between the bitmap bounds and the projected view bounds.
|
|
Matrix boundsToBitmapSpace = new Matrix();
|
|
mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
|
|
RectF boundsInBitmapSpace = new RectF();
|
|
boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
|
|
|
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
int bottomInset = dp.isTablet
|
|
? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0;
|
|
return Insets.of(0, 0, 0, bottomInset);
|
|
}
|
|
|
|
|
|
@SystemUiControllerFlags
|
|
public int getSysUiStatusNavFlags() {
|
|
if (mThumbnailData != null) {
|
|
int flags = 0;
|
|
flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_STATUS_BARS) != 0
|
|
? SystemUiController.FLAG_LIGHT_STATUS
|
|
: SystemUiController.FLAG_DARK_STATUS;
|
|
flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0
|
|
? SystemUiController.FLAG_LIGHT_NAV
|
|
: SystemUiController.FLAG_DARK_NAV;
|
|
return flags;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
super.onLayout(changed, left, top, right, bottom);
|
|
updateSplashView(mSplashViewDrawable);
|
|
}
|
|
|
|
@Override
|
|
protected void onDraw(Canvas canvas) {
|
|
RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
|
|
canvas.save();
|
|
canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
|
|
canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
|
|
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
|
|
drawOnCanvas(canvas,
|
|
-currentDrawnInsets.left,
|
|
-currentDrawnInsets.top,
|
|
getMeasuredWidth() + currentDrawnInsets.right,
|
|
getMeasuredHeight() + currentDrawnInsets.bottom,
|
|
mFullscreenParams.mCurrentDrawnCornerRadius);
|
|
canvas.restore();
|
|
}
|
|
|
|
public PreviewPositionHelper getPreviewPositionHelper() {
|
|
return mPreviewPositionHelper;
|
|
}
|
|
|
|
public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
|
|
mFullscreenParams = fullscreenParams;
|
|
getTaskOverlay().setFullscreenParams(fullscreenParams);
|
|
invalidate();
|
|
}
|
|
|
|
public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
|
|
float cornerRadius) {
|
|
if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
|
|
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
|
|
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
|
|
mDimmingPaintAfterClearing);
|
|
return;
|
|
}
|
|
|
|
// Always draw the background since the snapshots might be translucent or partially empty
|
|
// (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
|
|
// split screen).
|
|
canvas.drawRoundRect(x, y + 1, width, height - 1, cornerRadius,
|
|
cornerRadius, mBackgroundPaint);
|
|
|
|
final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
|
|
|| mThumbnailData == null;
|
|
if (drawBackgroundOnly) {
|
|
return;
|
|
}
|
|
|
|
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
|
|
|
|
// Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
|
|
if (shouldShowSplashView()) {
|
|
if (mSplashView != null) {
|
|
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
|
|
cornerRadius, mSplashBackgroundPaint);
|
|
|
|
mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
|
|
mSplashView.draw(canvas);
|
|
}
|
|
}
|
|
}
|
|
|
|
public TaskView getTaskView() {
|
|
return (TaskView) getParent();
|
|
}
|
|
|
|
public void setOverlayEnabled(boolean overlayEnabled) {
|
|
if (mOverlayEnabled != overlayEnabled) {
|
|
mOverlayEnabled = overlayEnabled;
|
|
|
|
refreshOverlay();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if the splash should be shown over top of the thumbnail.
|
|
*
|
|
* <p>We want to show the splash if the aspect ratio or rotation of the thumbnail would be
|
|
* different from the task.
|
|
*/
|
|
public boolean shouldShowSplashView() {
|
|
return isThumbnailAspectRatioDifferentFromThumbnailData()
|
|
|| isThumbnailRotationDifferentFromTask();
|
|
}
|
|
|
|
private void updateSplashView(Drawable icon) {
|
|
if (icon == null || icon.getConstantState() == null) {
|
|
return;
|
|
}
|
|
mSplashViewDrawable = icon.getConstantState().newDrawable().mutate();
|
|
mSplashViewDrawable.setAlpha(mSplashAlpha);
|
|
ImageView imageView = mSplashView == null ? new ImageView(getContext()) : mSplashView;
|
|
imageView.setImageDrawable(mSplashViewDrawable);
|
|
|
|
imageView.setScaleType(ImageView.ScaleType.MATRIX);
|
|
Matrix matrix = new Matrix();
|
|
|
|
float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
|
|
float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
|
|
float viewWidth = getMeasuredWidth();
|
|
float viewCenterX = viewWidth / 2f;
|
|
float viewHeight = getMeasuredHeight();
|
|
float viewCenterY = viewHeight / 2f;
|
|
float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f;
|
|
float centeredDrawableTop = (viewHeight - drawableHeight) / 2f;
|
|
float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
|
|
float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
|
|
? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
|
|
float scale = nonGridScale * recentsMaxScale;
|
|
|
|
// Center the image in the view.
|
|
matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
|
|
// Apply scale transformation after translation, pivoting around center of view.
|
|
matrix.postScale(scale, scale, viewCenterX, viewCenterY);
|
|
|
|
imageView.setImageMatrix(matrix);
|
|
mSplashView = imageView;
|
|
}
|
|
|
|
private boolean isThumbnailAspectRatioDifferentFromThumbnailData() {
|
|
if (mThumbnailData == null || mThumbnailData.thumbnail == null) {
|
|
return false;
|
|
}
|
|
|
|
float thumbnailViewAspect = getWidth() / (float) getHeight();
|
|
float thumbnailDataAspect =
|
|
mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
|
|
|
|
return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
|
|
thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
|
|
}
|
|
|
|
private boolean isThumbnailRotationDifferentFromTask() {
|
|
RecentsView recents = getTaskView().getRecentsView();
|
|
if (recents == null || mThumbnailData == null) {
|
|
return false;
|
|
}
|
|
|
|
if (recents.getPagedOrientationHandler() == PagedOrientationHandler.PORTRAIT) {
|
|
int currentRotation = recents.getPagedViewOrientedState().getRecentsActivityRotation();
|
|
return (currentRotation - mThumbnailData.rotation) % 2 != 0;
|
|
} else {
|
|
return recents.getPagedOrientationHandler().getRotation() != mThumbnailData.rotation;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Potentially re-init the task overlay. Be cautious when calling this as the overlay may
|
|
* do processing on initialization.
|
|
*/
|
|
private void refreshOverlay() {
|
|
if (mOverlayEnabled) {
|
|
getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
|
|
mPreviewPositionHelper.isOrientationChanged());
|
|
} else {
|
|
getTaskOverlay().reset();
|
|
}
|
|
}
|
|
|
|
private void updateThumbnailPaintFilter() {
|
|
ColorFilter filter = getColorFilter(mDimAlpha);
|
|
mBackgroundPaint.setColorFilter(filter);
|
|
int alpha = (int) (mDimAlpha * 255);
|
|
mDimmingPaintAfterClearing.setAlpha(alpha);
|
|
if (mBitmapShader != null) {
|
|
mPaint.setColorFilter(filter);
|
|
} else {
|
|
mPaint.setColorFilter(null);
|
|
mPaint.setColor(ColorUtils.blendARGB(Color.BLACK, mDimColor, alpha));
|
|
}
|
|
invalidate();
|
|
}
|
|
|
|
private void updateThumbnailMatrix() {
|
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
mPreviewPositionHelper.setTaskbarInApp(dp.isTaskbarPresentInApps);
|
|
mPreviewPositionHelper.setOrientationChanged(false);
|
|
if (mBitmapShader != null && mThumbnailData != null) {
|
|
mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
|
|
mThumbnailData.thumbnail.getHeight());
|
|
int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
|
|
.getRecentsActivityRotation();
|
|
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
|
|
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
|
|
getMeasuredWidth(), getMeasuredHeight(), dp.widthPx, dp.heightPx,
|
|
dp.taskbarSize, dp.isTablet, currentRotation, isRtl);
|
|
|
|
mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
|
|
mPaint.setShader(mBitmapShader);
|
|
}
|
|
getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
|
|
invalidate();
|
|
}
|
|
|
|
@Override
|
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
updateThumbnailMatrix();
|
|
|
|
refreshOverlay();
|
|
}
|
|
|
|
private ColorFilter getColorFilter(float dimAmount) {
|
|
return Utilities.makeColorTintingColorFilter(mDimColor, dimAmount);
|
|
}
|
|
|
|
/**
|
|
* Returns current thumbnail or null if none is set.
|
|
*/
|
|
@Nullable
|
|
public Bitmap getThumbnail() {
|
|
if (mThumbnailData == null) {
|
|
return null;
|
|
}
|
|
return mThumbnailData.thumbnail;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the snapshot is real. If the device is locked for the user of the task,
|
|
* the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
|
|
*/
|
|
public boolean isRealSnapshot() {
|
|
if (mThumbnailData == null) {
|
|
return false;
|
|
}
|
|
return mThumbnailData.isRealSnapshot && !mTask.isLocked;
|
|
}
|
|
}
|