Live tile should switch to screenshot before finishing recents animation

Fixes: 139439373
Test: Launch a new app from shortcuts in Overview mode. Make sure the
live tile stays put (no hole). Same with touching empty space in
Overview to go home.

Change-Id: I6cacf2842e21f9856d0021cea9fddf4f870f09f0
This commit is contained in:
Tracy Zhou 2019-08-14 15:01:53 -07:00
parent 67138875fa
commit 387279d938
10 changed files with 160 additions and 50 deletions

View File

@ -89,5 +89,5 @@ public abstract class RecentsUiFactory {
return RotationMode.NORMAL;
}
public static void clearSwipeSharedState(boolean finishAnimation) {}
public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
}

View File

@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import android.content.Context;
@ -196,11 +197,15 @@ public abstract class RecentsUiFactory {
return new RecentsViewStateController(launcher);
}
/**
* Clears the swipe shared state for the current swipe gesture.
*/
public static void clearSwipeSharedState(boolean finishAnimation) {
TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
/** Clears the swipe shared state for the current swipe gesture. */
public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
launcher.<RecentsView>getOverviewPanel().switchToScreenshot(
() -> TouchInteractionService.getSwipeSharedState().clearAllState(
finishAnimation));
} else {
TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
}
}
/**

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2019 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;
import android.graphics.Canvas;
import android.view.View;
import com.android.systemui.shared.system.WindowCallbacksCompat;
import java.util.function.BooleanSupplier;
/**
* Utility class for helpful methods related to {@link View} objects.
*/
public class ViewUtils {
/** See {@link #postDraw(View, Runnable, BooleanSupplier)}} */
public static boolean postDraw(View view, Runnable onFinishRunnable) {
return postDraw(view, onFinishRunnable, () -> false);
}
/**
* Inject some addition logic in order to make sure that the view is updated smoothly post
* draw, and allow addition task to be run after view update.
*
* @param onFinishRunnable runnable to be run right after the view finishes drawing.
*/
public static boolean postDraw(View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
return new WindowCallbacksCompat(view) {
// The number of frames to defer until we actually finish the animation
private int mDeferFrameCount = 2;
@Override
public void onPostDraw(Canvas canvas) {
// If we were cancelled after this was attached, do not update
// the state.
if (canceled.getAsBoolean()) {
detach();
return;
}
if (mDeferFrameCount > 0) {
mDeferFrameCount--;
// Workaround, detach and reattach to invalidate the root node for
// another draw
detach();
attach();
view.invalidate();
return;
}
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
detach();
}
}.attach();
}
}

View File

@ -45,7 +45,6 @@ import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
@ -90,12 +89,10 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.WindowCallbacksCompat;
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
extends BaseSwipeUpHandler<T, RecentsView>
implements OnApplyWindowInsetsListener {
extends BaseSwipeUpHandler<T, RecentsView> implements OnApplyWindowInsetsListener {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@ -1007,6 +1004,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
}
public boolean isCanceled() {
return mCanceled;
}
@UiThread
private void resumeLastTask() {
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
@ -1099,14 +1100,21 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
private void switchToScreenshot() {
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (controller != null) {
// Update the screenshot of the task
if (mTaskSnapshot == null) {
mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
}
mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
}
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else if (!mRecentsAnimationWrapper.hasTargets()) {
// If there are no targets, then we don't need to capture anything
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
boolean finishTransitionPosted = false;
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
if (controller != null) {
// Update the screenshot of the task
if (mTaskSnapshot == null) {
@ -1123,34 +1131,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
if (taskView != null && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
finishTransitionPosted = new WindowCallbacksCompat(taskView) {
// The number of frames to defer until we actually finish the animation
private int mDeferFrameCount = 2;
@Override
public void onPostDraw(Canvas canvas) {
// If we were cancelled after this was attached, do not update
// the state.
if (mCanceled) {
detach();
return;
}
if (mDeferFrameCount > 0) {
mDeferFrameCount--;
// Workaround, detach and reattach to invalidate the root node for
// another draw
detach();
attach();
taskView.invalidate();
return;
}
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
detach();
}
}.attach();
finishTransitionPosted = ViewUtils.postDraw(taskView,
() -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled);
}
}
if (!finishTransitionPosted) {

View File

@ -78,7 +78,12 @@ public class LauncherRecentsView extends RecentsView<Launcher> implements StateL
@Override
public void startHome() {
mActivity.getStateManager().goToState(NORMAL);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
switchToScreenshot(() -> finishRecentsAnimation(true /* toRecents */,
() -> mActivity.getStateManager().goToState(NORMAL)));
} else {
mActivity.getStateManager().goToState(NORMAL);
}
}
@Override

View File

@ -107,6 +107,7 @@ import com.android.quickstep.RecentsModel;
import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@ -380,14 +381,23 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
return null;
}
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
/**
* Update the thumbnail of the task.
* @param refreshNow Refresh immediately if it's true.
*/
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
TaskView taskView = getTaskView(taskId);
if (taskView != null) {
taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
}
return taskView;
}
/** See {@link #updateThumbnail(int, ThumbnailData, boolean)} */
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
return updateThumbnail(taskId, thumbnailData, true /* refreshNow */);
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
@ -1819,4 +1829,19 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
final WindowInsets insets = getRootWindowInsets();
return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
}
/** If it's in the live tile mode, switch the running task into screenshot mode. */
public void switchToScreenshot(Runnable onFinishRunnable) {
TaskView taskView = getRunningTaskView();
if (taskView == null) {
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
return;
}
taskView.setShowScreenshot(true);
taskView.getThumbnail().refresh();
ViewUtils.postDraw(taskView, onFinishRunnable);
}
}

View File

@ -134,16 +134,35 @@ public class TaskThumbnailView extends View implements PluginListener<OverviewSc
}
/**
* Updates this thumbnail.
* 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(Task task, ThumbnailData thumbnailData) {
public void setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow) {
mTask = task;
if (thumbnailData != null && thumbnailData.thumbnail != null) {
Bitmap bm = thumbnailData.thumbnail;
mThumbnailData =
(thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
if (refreshNow) {
refresh();
}
}
/** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
public void setThumbnail(Task task, ThumbnailData thumbnailData) {
setThumbnail(task, thumbnailData, true /* refreshNow */);
}
/** Updates the shader, paint, matrix to redraw. */
public void refresh() {
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);
mThumbnailData = thumbnailData;
updateThumbnailMatrix();
} else {
mBitmapShader = null;

View File

@ -53,7 +53,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;

View File

@ -1893,7 +1893,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// recents animation into launcher. Defer launching the activity until Launcher is
// next resumed.
addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
UiFactory.clearSwipeSharedState(true /* finishAnimation */);
UiFactory.clearSwipeSharedState(this, true /* finishAnimation */);
return true;
}

View File

@ -96,7 +96,8 @@ public class UiFactory {
public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
public static void clearSwipeSharedState(boolean finishAnimation) {}
/** No-op. */
public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
public static Person[] getPersons(ShortcutInfo si) {
return Utilities.EMPTY_PERSON_ARRAY;