Merging from ub-launcher3-master @ build 6925377

Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-master_master_6925377.html

Change-Id: I928b100c8f41abff34047df69d988622123f9939
This commit is contained in:
Winson Chung
2020-10-23 09:32:16 -07:00
100 changed files with 3111 additions and 2461 deletions
@@ -16,14 +16,10 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
@@ -32,11 +28,8 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@@ -53,60 +46,16 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
protected boolean isLaunchingFromRecents(@NonNull View v,
@Nullable RemoteAnimationTargetCompat[] targets) {
return mLauncher.getStateManager().getState().overviewUi
&& findTaskViewToLaunch(mLauncher, v, targets) != null;
&& findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
}
@Override
protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
RecentsView recentsView = mLauncher.getOverviewPanel();
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
mLauncher.getDepthController(), pa);
anim.play(pa.buildAnim());
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLauncher.getStateManager().moveToRestState();
mLauncher.getStateManager().reapplyState();
}
};
} else {
AnimatorPlaybackController controller =
mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL,
RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
childStateAnimation = controller.getTarget();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLauncher.getStateManager().goToState(NORMAL, false);
}
};
}
anim.play(launcherAnim);
// Set the current animation first, before adding windowAnimEndListener. Setting current
// animation adds some listeners which need to be called before windowAnimEndListener
// (the ordering of listeners matter in this case).
mLauncher.getStateManager().setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
mLauncher.getDepthController());
}
@Override
@@ -153,6 +153,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
private final float mContentTransY;
private final float mWorkspaceTransY;
private final float mClosingWindowTransY;
private final float mMaxShadowRadius;
private DeviceProfile mDeviceProfile;
@@ -186,6 +187,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
mLauncher.addOnDeviceProfileChangeListener(this);
}
@@ -538,6 +540,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
EXAGGERATED_EASE);
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, windowRadius, 0,
RADIUS_DURATION, EXAGGERATED_EASE);
FloatProp mShadowRadius = new FloatProp(0, mMaxShadowRadius, 0,
APP_LAUNCH_DURATION, EXAGGERATED_EASE);
@Override
public void onUpdate(float percent) {
@@ -600,7 +604,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
builder.withMatrix(matrix)
.withWindowCrop(crop)
.withAlpha(1f - mIconAlpha.value)
.withCornerRadius(mWindowRadius.value);
.withCornerRadius(mWindowRadius.value)
.withShadowRadius(mShadowRadius.value);
} else {
tmpPos.set(target.position.x, target.position.y);
if (target.localBounds != null) {
@@ -752,6 +757,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
FloatProp mShadowRadius = new FloatProp(mMaxShadowRadius, 0, 0, duration,
DEACCEL_1_7);
@Override
public void onUpdate(float percent) {
@@ -773,7 +780,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
matrix.postTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
.withAlpha(mAlpha.value)
.withCornerRadius(windowCornerRadius);
.withCornerRadius(windowCornerRadius)
.withShadowRadius(mShadowRadius.value);
} else {
matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
@@ -798,33 +806,31 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
}
private void addCujInstrumentation(Animator anim, int cuj, String transition) {
if (Trace.isEnabled()) {
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
Trace.beginAsyncSection(transition, 0);
InteractionJankMonitorWrapper.begin(cuj);
super.onAnimationStart(animation);
}
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
Trace.beginAsyncSection(transition, 0);
InteractionJankMonitorWrapper.begin(cuj);
super.onAnimationStart(animation);
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
InteractionJankMonitorWrapper.cancel(cuj);
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
InteractionJankMonitorWrapper.cancel(cuj);
}
@Override
public void onAnimationSuccess(Animator animator) {
InteractionJankMonitorWrapper.end(cuj);
}
@Override
public void onAnimationSuccess(Animator animator) {
InteractionJankMonitorWrapper.end(cuj);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
}
});
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
}
});
}
/**
@@ -108,6 +108,8 @@ public class PredictionRowView extends LinearLayout implements
AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
@Nullable private List<ItemInfo> mPendingPredictedItems;
public PredictionRowView(@NonNull Context context) {
this(context, null);
}
@@ -164,6 +166,7 @@ public class PredictionRowView extends LinearLayout implements
}
mDecorationHandler.onDraw(canvas);
mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
mLauncher.getAppsView().getActiveRecyclerView().invalidateItemDecorations();
}
mFocusHelper.draw(canvas);
super.dispatchDraw(canvas);
@@ -203,6 +206,16 @@ public class PredictionRowView extends LinearLayout implements
* we can optimize by swapping them in place.
*/
public void setPredictedApps(List<ItemInfo> items) {
if (!mLauncher.isWorkspaceLoading() && isShown() && getWindowVisibility() == View.VISIBLE) {
mPendingPredictedItems = items;
return;
}
applyPredictedApps(items);
}
private void applyPredictedApps(List<ItemInfo> items) {
mPendingPredictedItems = null;
mPredictedApps.clear();
items.stream()
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
@@ -341,4 +354,13 @@ public class PredictionRowView extends LinearLayout implements
public View getFocusedChild() {
return getChildAt(0);
}
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
if (mPendingPredictedItems != null && !isVisible) {
applyPredictedApps(mPendingPredictedItems);
}
}
}
@@ -251,6 +251,28 @@ public class HotseatPredictionController implements DragController.DragListener,
* Sets or updates the predicted items
*/
public void setPredictedItems(FixedContainerItems items) {
if (!mLauncher.isWorkspaceLoading()
&& mHotseat.isShown()
&& mHotseat.getWindowVisibility() == View.VISIBLE) {
mHotseat.setOnVisibilityAggregatedCallback((isVisible) -> {
if (isVisible) {
return;
}
mHotseat.setOnVisibilityAggregatedCallback(null);
applyPredictedItems(items);
});
} else {
mHotseat.setOnVisibilityAggregatedCallback(null);
applyPredictedItems(items);
}
}
/**
* Sets or updates the predicted items only once the hotseat becomes hidden to the user
*/
private void applyPredictedItems(FixedContainerItems items) {
mPredictedItems = items.items;
if (mPredictedItems.isEmpty()) {
HotseatRestoreHelper.restoreBackup(mLauncher);
@@ -250,6 +250,9 @@ public class AppEventProducer implements StatsLogConsumer {
case PREDICTION_CONTAINER: {
return "predictions";
}
case SHORTCUTS_CONTAINER: {
return "deep-shortcuts";
}
case FOLDER: {
FolderContainer fc = ci.getFolder();
switch (fc.getParentContainerCase()) {
@@ -18,8 +18,7 @@ package com.android.launcher3.model;
import static android.content.ContentResolver.SCHEME_CONTENT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static com.android.launcher3.Utilities.newContentObserver;
import android.annotation.TargetApi;
import android.app.RemoteAction;
@@ -35,7 +34,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Message;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -43,7 +42,7 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
@@ -55,6 +54,7 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.BgObjectWithLooper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
@@ -68,15 +68,11 @@ import java.util.Map;
* Data model for digital wellbeing status of apps.
*/
@TargetApi(Build.VERSION_CODES.Q)
public final class WellbeingModel {
public final class WellbeingModel extends BgObjectWithLooper {
private static final String TAG = "WellbeingModel";
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
private static final boolean DEBUG = false;
private static final int MSG_PACKAGE_ADDED = 1;
private static final int MSG_PACKAGE_REMOVED = 2;
private static final int MSG_FULL_REFRESH = 3;
private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
private static final int IN_MINIMAL_DEVICE = 2;
@@ -98,9 +94,9 @@ public final class WellbeingModel {
private final Context mContext;
private final String mWellbeingProviderPkg;
private final Handler mWorkerHandler;
private final ContentObserver mContentObserver;
private Handler mWorkerHandler;
private ContentObserver mContentObserver;
private final Object mModelLock = new Object();
// Maps the action Id to the corresponding RemoteAction
@@ -111,60 +107,73 @@ public final class WellbeingModel {
private WellbeingModel(final Context context) {
mContext = context;
mWorkerHandler =
new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage);
mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (DEBUG || mIsInTest) {
Log.d(TAG, "ContentObserver.onChange() called with: selfChange = ["
+ selfChange + "], uri = [" + uri + "]");
}
Preconditions.assertUIThread();
if (uri.getPath().contains(PATH_ACTIONS)) {
// Wellbeing reports that app actions have changed.
updateWellbeingData();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
updateLauncherModel(context);
}
}
};
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
updateLauncherModel(context));
initializeInBackground("WellbeingHandler");
}
@Override
protected void onInitialized(Looper looper) {
mWorkerHandler = new Handler(looper);
mContentObserver = newContentObserver(mWorkerHandler, this::onWellbeingUriChanged);
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
context.registerReceiver(
new SimpleBroadcastReceiver(this::onWellbeingProviderChanged),
mContext.registerReceiver(
new SimpleBroadcastReceiver(t -> restartObserver()),
PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg,
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED,
Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED,
Intent.ACTION_PACKAGE_RESTARTED));
Intent.ACTION_PACKAGE_RESTARTED),
null, mWorkerHandler);
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
filter);
mContext.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
filter, null, mWorkerHandler);
restartObserver();
}
}
@WorkerThread
private void onWellbeingUriChanged(Uri uri) {
Preconditions.assertNonUiThread();
if (DEBUG || mIsInTest) {
Log.d(TAG, "ContentObserver.onChange() called with: uri = [" + uri + "]");
}
if (uri.getPath().contains(PATH_ACTIONS)) {
// Wellbeing reports that app actions have changed.
updateAllPackages();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
return;
}
// Temporary bug fix for b/169771796. Wellbeing provides the layout configuration when
// minimal device is enabled. We always want to reload the configuration from Wellbeing
// since the layout configuration might have changed.
mContext.deleteDatabase(DB_NAME_MINIMAL_DEVICE);
final Bundle extras = new Bundle();
String dbFile;
if (isInMinimalDeviceMode()) {
dbFile = DB_NAME_MINIMAL_DEVICE;
extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
mWellbeingProviderPkg + ".api");
} else {
dbFile = InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
}
LauncherSettings.Settings.call(mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
dbFile, extras);
}
}
public void setInTest(boolean inTest) {
mIsInTest = inTest;
}
protected void onWellbeingProviderChanged(Intent intent) {
if (DEBUG || mIsInTest) {
Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]");
}
restartObserver();
}
@WorkerThread
private void restartObserver() {
final ContentResolver resolver = mContext.getContentResolver();
resolver.unregisterContentObserver(mContentObserver);
@@ -179,7 +188,7 @@ public final class WellbeingModel {
Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
}
updateWellbeingData();
updateAllPackages();
}
@MainThread
@@ -212,39 +221,6 @@ public final class WellbeingModel {
}
}
private void updateWellbeingData() {
mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
}
private void updateLauncherModel(@NonNull final Context context) {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
reloadLauncherInNormalMode(context);
return;
}
mWorkerHandler.post(() -> {
if (isInMinimalDeviceMode()) {
reloadLauncherInMinimalMode(context);
} else {
reloadLauncherInNormalMode(context);
}
});
}
private void reloadLauncherInNormalMode(@NonNull final Context context) {
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
InvariantDeviceProfile.INSTANCE.get(context).dbFile);
}
private void reloadLauncherInMinimalMode(@NonNull final Context context) {
final Bundle extras = new Bundle();
extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
mWellbeingProviderPkg + ".api");
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
DB_NAME_MINIMAL_DEVICE, extras);
}
private Uri.Builder apiBuilder() {
return new Uri.Builder()
.scheme(SCHEME_CONTENT)
@@ -277,7 +253,8 @@ public final class WellbeingModel {
return false;
}
private boolean updateActions(String... packageNames) {
@WorkerThread
private boolean updateActions(String[] packageNames) {
if (packageNames.length == 0) {
return true;
}
@@ -340,68 +317,51 @@ public final class WellbeingModel {
return true;
}
private boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_PACKAGE_REMOVED: {
String packageName = (String) msg.obj;
mWorkerHandler.removeCallbacksAndMessages(packageName);
synchronized (mModelLock) {
mPackageToActionId.remove(packageName);
}
return true;
}
case MSG_PACKAGE_ADDED: {
String packageName = (String) msg.obj;
mWorkerHandler.removeCallbacksAndMessages(packageName);
if (!updateActions(packageName)) {
scheduleRefreshRetry(msg);
}
return true;
}
@WorkerThread
private void updateActionsWithRetry(int retryCount, @Nullable String packageName) {
String[] packageNames = TextUtils.isEmpty(packageName)
? mContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle()).stream()
.map(li -> li.getApplicationInfo().packageName).distinct()
.toArray(String[]::new)
: new String[] { packageName };
case MSG_FULL_REFRESH: {
// Remove all existing messages
mWorkerHandler.removeCallbacksAndMessages(null);
final String[] packageNames = mContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle()).stream()
.map(li -> li.getApplicationInfo().packageName).distinct()
.toArray(String[]::new);
if (!updateActions(packageNames)) {
scheduleRefreshRetry(msg);
}
return true;
}
mWorkerHandler.removeCallbacksAndMessages(packageName);
if (updateActions(packageNames)) {
return;
}
return false;
}
private void scheduleRefreshRetry(Message originalMsg) {
int retryCount = originalMsg.arg1;
if (retryCount >= RETRY_TIMES_MS.length) {
// To many retries, skip
return;
}
Message msg = Message.obtain(originalMsg);
msg.arg1 = retryCount + 1;
mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]);
mWorkerHandler.postDelayed(
() -> updateActionsWithRetry(retryCount + 1, packageName),
packageName, RETRY_TIMES_MS[retryCount]);
}
@WorkerThread
private void updateAllPackages() {
updateActionsWithRetry(0, null);
}
@WorkerThread
private void onAppPackageChanged(Intent intent) {
if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]");
Preconditions.assertUIThread();
Preconditions.assertNonUiThread();
final String packageName = intent.getData().getSchemeSpecificPart();
if (packageName == null || packageName.length() == 0) {
// they sent us a bad intent
return;
}
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget();
mWorkerHandler.removeCallbacksAndMessages(packageName);
synchronized (mModelLock) {
mPackageToActionId.remove(packageName);
}
} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget();
updateActionsWithRetry(0, packageName);
}
}
@@ -41,11 +41,14 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINI
import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -78,6 +81,8 @@ import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
@@ -90,6 +95,7 @@ import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
@@ -107,6 +113,7 @@ import com.android.systemui.shared.system.TaskInfoCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
/**
@@ -614,13 +621,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
updateSysUiFlags(mCurrentShift.value);
applyWindowTransform();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mRecentsAnimationTargets != null) {
LiveTileOverlay.INSTANCE.update(
mTaskViewSimulator.getCurrentRect(),
mTaskViewSimulator.getCurrentCornerRadius());
}
}
updateLauncherTransitionProgress();
}
@@ -719,6 +719,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
@UiThread
public void onGestureStarted(boolean isLikelyToStartNewTask) {
InteractionJankMonitorWrapper.begin(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
notifyGestureStartedAsync();
setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
@@ -794,7 +796,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
}
private void onSettledOnEndTarget() {
switch (mGestureState.getEndTarget()) {
final GestureEndTarget endTarget = mGestureState.getEndTarget();
switch (endTarget) {
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify swipe-to-home (recents animation) is finished
@@ -811,7 +814,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
mStateCallback.setState(STATE_RESUME_LAST_TASK);
break;
}
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
}
/** @return Whether this was the task we were waiting to appear, and thus handled it. */
@@ -1388,7 +1394,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
if (taskView != null && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
finishTransitionPosted = ViewUtils.postDraw(taskView,
finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
this::isCanceled);
}
@@ -1450,17 +1456,64 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete();
if (mRecentsAnimationController != null) {
mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
true /* screenshot */);
}
mRecentsView.onSwipeUpAnimationSuccess();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
this::launchOtherTaskInLiveTileMode);
}
SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
reset();
}
private void launchOtherTaskInLiveTileMode(RemoteAnimationTargetCompat appearedTaskTarget) {
TaskView taskView = mRecentsView.getTaskView(appearedTaskTarget.taskId);
if (taskView == null) {
return;
}
RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
mRecentsAnimationTargets.apps,
mRecentsAnimationTargets.apps.length + 1);
apps[apps.length - 1] = appearedTaskTarget;
boolean launcherClosing =
taskIsATargetWithMode(apps, mActivity.getTaskId(), MODE_CLOSING);
AnimatorSet anim = new AnimatorSet();
TaskViewUtils.composeRecentsLaunchAnimator(
anim, taskView, apps,
mRecentsAnimationTargets.wallpapers, launcherClosing,
mActivity.getStateManager(), mRecentsView,
mActivityInterface.getDepthController());
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animator) {
cleanUp(false);
}
@Override
public void onAnimationCancel(Animator animator) {
cleanUp(true);
}
private void cleanUp(boolean canceled) {
if (mRecentsAnimationController != null) {
mRecentsAnimationController.finish(false /* toRecents */,
null /* onFinishComplete */);
if (canceled) {
mRecentsAnimationController = null;
} else {
mActivityInterface.onLaunchTaskSuccess();
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
}
}
});
anim.start();
}
private void addLiveTileOverlay() {
if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
mRecentsView.setLiveTileOverlayAttached(true);
@@ -1523,39 +1576,33 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
protected void startNewTask(Consumer<Boolean> resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
true /* freezeTaskList */);
} else {
if (!mCanceled) {
TaskView nextTask = mRecentsView.getNextPageTaskView();
if (nextTask != null) {
int taskId = nextTask.getTask().key.id;
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
success -> {
resultCallback.accept(success);
if (success) {
if (hasTaskPreviouslyAppeared) {
onRestartPreviouslyAppearedTask();
}
} else {
mActivityInterface.onLaunchTaskFailed();
nextTask.notifyTaskLaunchFailed(TAG);
mRecentsAnimationController.finish(true /* toRecents */, null);
if (!mCanceled) {
TaskView nextTask = mRecentsView.getNextPageTaskView();
if (nextTask != null) {
int taskId = nextTask.getTask().key.id;
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
success -> {
resultCallback.accept(success);
if (success) {
if (hasTaskPreviouslyAppeared) {
onRestartPreviouslyAppearedTask();
}
}, MAIN_EXECUTOR.getHandler());
} else {
mActivityInterface.onLaunchTaskFailed();
Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
mRecentsAnimationController.finish(true /* toRecents */, null);
}
} else {
mActivityInterface.onLaunchTaskFailed();
nextTask.notifyTaskLaunchFailed(TAG);
mRecentsAnimationController.finish(true /* toRecents */, null);
}
}, MAIN_EXECUTOR.getHandler());
} else {
mActivityInterface.onLaunchTaskFailed();
Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
mRecentsAnimationController.finish(true /* toRecents */, null);
}
mCanceled = false;
}
mCanceled = false;
}
/**
@@ -1641,6 +1688,32 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
}
mTaskViewSimulator.apply(mTransformParams);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mRecentsAnimationTargets != null) {
LiveTileOverlay.INSTANCE.update(
mTaskViewSimulator.getCurrentRect(),
mTaskViewSimulator.getCurrentCornerRadius());
}
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param inputConsumerProto The parent of this proto message.
*/
public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
mGestureState.writeToProto(swipeHandlerProto);
swipeHandlerProto.setIsRecentsAttachedToAppWindow(
mAnimationFactory.isRecentsAttachedToAppWindow());
swipeHandlerProto.setScrollOffset(mRecentsView == null
? 0
: mRecentsView.getScrollOffset());
swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
inputConsumerProto.setSwipeHandler(swipeHandlerProto);
}
public interface Factory {
@@ -297,6 +297,10 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
* @param animate Whether to animate recents to/from its new attached state.
*/
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
default boolean isRecentsAttachedToAppWindow() {
return false;
}
}
class DefaultAnimationFactory implements AnimationFactory {
@@ -388,6 +392,11 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
}
@Override
public boolean isRecentsAttachedToAppWindow() {
return mIsAttachedToWindow;
}
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
// Scale down recents from being full screen to being in overview.
RecentsView recentsView = activity.getOverviewPanel();
@@ -26,6 +26,8 @@ import android.content.Intent;
import android.os.Build;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.GestureStateProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -46,19 +48,22 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
* Defines the end targets of a gesture and the associated state.
*/
public enum GestureEndTarget {
HOME(true, LAUNCHER_STATE_HOME, false),
HOME(true, LAUNCHER_STATE_HOME, false, GestureStateProto.GestureEndTarget.HOME),
RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
RECENTS(true, LAUNCHER_STATE_OVERVIEW, true, GestureStateProto.GestureEndTarget.RECENTS),
NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
GestureStateProto.GestureEndTarget.NEW_TASK),
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true);
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
GestureStateProto.GestureEndTarget.LAST_TASK);
GestureEndTarget(boolean isLauncher, int containerType,
boolean recentsAttachedToAppWindow) {
GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
GestureStateProto.GestureEndTarget protoEndTarget) {
this.isLauncher = isLauncher;
this.containerType = containerType;
this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
this.protoEndTarget = protoEndTarget;
}
/** Whether the target is in the launcher activity. Implicitly, if the end target is going
@@ -68,6 +73,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
public final int containerType;
/** Whether RecentsView should be attached to the window as we animate to this target */
public final boolean recentsAttachedToAppWindow;
/** The GestureStateProto enum value, used for winscope tracing. See launcher_trace.proto */
public final GestureStateProto.GestureEndTarget protoEndTarget;
}
private static final String TAG = "GestureState";
@@ -345,4 +352,17 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
pw.println(" lastStartedTaskId=" + mLastStartedTaskId);
pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param swipeHandlerProto The parent of this proto message.
*/
public void writeToProto(SwipeHandlerProto.Builder swipeHandlerProto) {
GestureStateProto.Builder gestureStateProto = GestureStateProto.newBuilder();
gestureStateProto.setEndTarget(mEndTarget == null
? GestureStateProto.GestureEndTarget.UNSET
: mEndTarget.protoEndTarget);
swipeHandlerProto.setGestureState(gestureStateProto);
}
}
@@ -21,6 +21,9 @@ import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
@TargetApi(Build.VERSION_CODES.O)
public interface InputConsumer {
@@ -116,4 +119,21 @@ public interface InputConsumer {
}
return name.toString();
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param serviceProto The parent of this proto message.
*/
default void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
InputConsumerProto.Builder inputConsumerProto = InputConsumerProto.newBuilder();
inputConsumerProto.setName(getName());
writeToProtoInternal(inputConsumerProto);
serviceProto.setInputConsumer(inputConsumerProto);
}
/**
* @see #writeToProto - allows subclasses to write additional info to the proto.
*/
default void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {}
}
@@ -35,6 +35,7 @@ import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -174,6 +175,9 @@ public class OverviewCommandHelper {
return;
}
InteractionJankMonitorWrapper.begin(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timout */);
// Otherwise, start overview.
mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
@@ -35,6 +35,8 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.SparseIntArray;
import com.android.launcher3.tracing.OverviewComponentObserverProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -262,4 +264,17 @@ public final class OverviewComponentObserver {
pw.println(" overviewIntent=" + mOverviewIntent);
pw.println(" homeIntent=" + mCurrentHomeIntent);
}
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
* @param serviceProto The parent of this proto message.
*/
public void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
OverviewComponentObserverProto.Builder overviewComponentObserver =
OverviewComponentObserverProto.newBuilder();
overviewComponentObserver.setOverviewActivityStarted(mActivityInterface.isStarted());
overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
}
}
@@ -25,6 +25,7 @@ import androidx.annotation.UiThread;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -90,24 +91,6 @@ public class RecentsAnimationController {
}
}
/**
* Notifies the controller that we want to defer cancel until the next app transition starts.
* If {@param screenshot} is set, then we will receive a screenshot on the next
* {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)} and we must also call
* {@link #cleanupScreenshot()} when that screenshot is no longer used.
*/
public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
mController.setDeferCancelUntilNextTransition(defer, screenshot);
}
/**
* Cleans up the screenshot previously returned from
* {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)}.
*/
public void cleanupScreenshot() {
UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
}
/**
* Remove task remote animation target from
* {@link RecentsAnimationCallbacks#onTaskAppeared(RemoteAnimationTargetCompat)}}.
@@ -151,6 +134,7 @@ public class RecentsAnimationController {
mOnFinishedListener.accept(this);
UI_HELPER_EXECUTOR.execute(() -> {
mController.finish(toRecents, sendUserLeaveHint);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
if (callback != null) {
MAIN_EXECUTOR.execute(callback);
}
@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -66,6 +67,8 @@ public abstract class SwipeUpAnimationLogic {
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
protected final float mMaxShadowRadius;
protected AnimatorControllerWithResistance mWindowTransitionController;
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
@@ -80,6 +83,9 @@ public abstract class SwipeUpAnimationLogic {
mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
mDeviceState.getRotationTouchHelper().getDisplayRotation());
mTaskViewSimulator.setDrawsBelowRecents(true);
mMaxShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.max_shadow_radius);
mTransformParams.setShadowRadius(mMaxShadowRadius);
}
protected void initTransitionEndpoints(DeviceProfile dp) {
@@ -259,9 +265,11 @@ public abstract class SwipeUpAnimationLogic {
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float shadowRadius = Utilities.mapRange(progress, mMaxShadowRadius, 0);
mTransformParams
.setTargetAlpha(getWindowAlpha(progress))
.setCornerRadius(cornerRadius);
.setCornerRadius(cornerRadius)
.setShadowRadius(shadowRadius);
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
@@ -272,7 +280,8 @@ public abstract class SwipeUpAnimationLogic {
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mCropRect)
.withCornerRadius(params.getCornerRadius());
.withCornerRadius(params.getCornerRadius())
.withShadowRadius(params.getShadowRadius());
}
@Override
@@ -17,6 +17,7 @@ package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
@@ -31,6 +32,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.function.Consumer;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
private RecentsAnimationController mController;
@@ -39,6 +42,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
/**
* Preloads the recents animation.
@@ -88,22 +92,21 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
if (thumbnailData != null) {
// If a screenshot is provided, switch to the screenshot before cleaning up
activityInterface.switchRunningTaskViewToScreenshot(thumbnailData,
() -> cleanUpRecentsAnimation(thumbnailData));
} else {
cleanUpRecentsAnimation(null /* canceledThumbnail */);
}
cleanUpRecentsAnimation();
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
cleanUpRecentsAnimation(null /* canceledThumbnail */);
cleanUpRecentsAnimation();
}
@Override
public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
if (mLaunchOtherTaskHandler != null
&& mLastGestureState.getEndTarget() == RECENTS) {
mLaunchOtherTaskHandler.accept(appearedTaskTarget);
return;
}
if (mController != null) {
if (mLastAppearedTaskTarget == null
|| appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) {
@@ -138,6 +141,15 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
return mCallbacks;
}
/**
* The passed-in handler is used to render side task launch animation in recents in live tile
* mode.
*/
public void setLaunchOtherTaskInLiveTileModeHandler(
Consumer<RemoteAnimationTargetCompat> handler) {
mLaunchOtherTaskHandler = handler;
}
/**
* Finishes the running recents animation.
*/
@@ -147,7 +159,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
? mController::finishAnimationToHome
: mController::finishAnimationToApp);
cleanUpRecentsAnimation(null /* canceledThumbnail */);
cleanUpRecentsAnimation();
}
}
@@ -174,12 +186,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
/**
* Cleans up the recents animation entirely.
*/
private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) {
// Clean up the screenshot if necessary
if (mController != null && canceledThumbnail != null) {
mController.cleanupScreenshot();
}
private void cleanUpRecentsAnimation() {
// Release all the target leashes
if (mTargets != null) {
mTargets.release();
@@ -195,6 +202,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mTargets = null;
mLastGestureState = null;
mLastAppearedTaskTarget = null;
mLaunchOtherTaskHandler = null;
}
public void dump() {
@@ -21,12 +21,14 @@ import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_REC
import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityManager;
@@ -34,6 +36,7 @@ import androidx.annotation.WorkerThread;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
@@ -42,7 +45,6 @@ import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskDescriptionCompat;
@@ -163,9 +165,8 @@ public class TaskIconCache {
key.getComponent(), key.userId);
}
if (activityInfo != null) {
entry.contentDescription = ActivityManagerWrapper.getInstance()
.getBadgedContentDescription(activityInfo, task.key.userId,
task.taskDescription);
entry.contentDescription = getBadgedContentDescription(
activityInfo, task.key.userId, task.taskDescription);
}
}
@@ -173,6 +174,21 @@ public class TaskIconCache {
return entry;
}
private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) {
PackageManager pm = mContext.getPackageManager();
String taskLabel = td == null ? null : Utilities.trim(td.getLabel());
if (TextUtils.isEmpty(taskLabel)) {
taskLabel = Utilities.trim(info.loadLabel(pm));
}
String applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(pm));
String badgedApplicationLabel = userId != UserHandle.myUserId()
? pm.getUserBadgedLabel(applicationLabel, UserHandle.of(userId)).toString()
: applicationLabel;
return applicationLabel.equals(taskLabel)
? badgedApplicationLabel : badgedApplicationLabel + " " + taskLabel;
}
@WorkerThread
private Drawable getDefaultIcon(int userId) {
synchronized (mDefaultIcons) {
@@ -17,15 +17,20 @@ package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
@@ -35,12 +40,18 @@ import android.graphics.RectF;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
@@ -49,6 +60,7 @@ import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@@ -67,8 +79,7 @@ public final class TaskViewUtils {
* opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
*/
public static TaskView findTaskViewToLaunch(
BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
RecentsView recentsView = activity.getOverviewPanel();
RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets) {
if (v instanceof TaskView) {
TaskView taskView = (TaskView) v;
return recentsView.isTaskViewVisible(taskView) ? taskView : null;
@@ -119,6 +130,21 @@ public final class TaskViewUtils {
return taskView;
}
public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
PendingAnimation out) {
boolean isRunningTask = v.isRunningTask();
TransformParams params = null;
TaskViewSimulator tsv = null;
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
params = v.getRecentsView().getLiveTileParams();
tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
}
createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets,
depthController, out, params, tsv);
}
/**
* Creates an animation that controls the window of the opening targets for the recents launch
* animation.
@@ -126,16 +152,25 @@ public final class TaskViewUtils {
public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
PendingAnimation out) {
PendingAnimation out, @Nullable TransformParams params,
@Nullable TaskViewSimulator tsv) {
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
boolean inLiveTileMode =
ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
targets.addReleaseCheck(applier);
new RemoteAnimationTargets(appTargets, wallpaperTargets,
inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
TransformParams params = new TransformParams()
if (params == null) {
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
targets.addReleaseCheck(applier);
params = new TransformParams()
.setSyncTransactionApplier(applier)
.setTargetSet(targets);
}
final RecentsView recentsView = v.getRecentsView();
int taskIndex = recentsView.indexOfChild(v);
@@ -149,8 +184,9 @@ public final class TaskViewUtils {
int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
TaskViewSimulator topMostSimulator = null;
if (targets.apps.length > 0) {
TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
if (tsv == null && targets.apps.length > 0) {
tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
tsv.setDp(dp);
tsv.setLayoutRotation(displayRotation, displayRotation);
tsv.setPreview(targets.apps[targets.apps.length - 1]);
@@ -158,19 +194,24 @@ public final class TaskViewUtils {
tsv.recentsViewScale.value = 1;
tsv.setScroll(startScroll);
// Fade in the task during the initial 20% of the animation
out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
clampToProgress(LINEAR, 0, 0.2f));
}
if (tsv != null) {
out.setFloat(tsv.fullScreenProgress,
AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
out.setFloat(tsv.recentsViewScale,
AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
out.addOnFrameCallback(() -> tsv.apply(params));
TaskViewSimulator finalTsv = tsv;
TransformParams finalParams = params;
out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
topMostSimulator = tsv;
}
// Fade in the task during the initial 20% of the animation
out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
@@ -223,10 +264,19 @@ public final class TaskViewUtils {
});
}
out.addListener(new AnimatorListenerAdapter() {
out.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (isQuickSwitch) {
InteractionJankMonitorWrapper.end(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
}
@Override
public void onAnimationEnd(Animator animation) {
targets.release();
super.onAnimationEnd(animation);
}
});
@@ -235,4 +285,57 @@ public final class TaskViewUtils {
TOUCH_RESPONSE_INTERPOLATOR);
}
}
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
@NonNull DepthController depthController) {
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
depthController, pa);
anim.play(pa.buildAnim());
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
stateManager.moveToRestState();
stateManager.reapplyState();
}
};
} else {
AnimatorPlaybackController controller =
stateManager.createAnimationToNewWorkspace(NORMAL,
RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
childStateAnimation = controller.getTarget();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
stateManager.goToState(NORMAL, false);
}
};
}
anim.play(launcherAnim);
// Set the current animation first, before adding windowAnimEndListener. Setting current
// animation adds some listeners which need to be called before windowAnimEndListener
// (the ordering of listeners matter in this case).
stateManager.setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
}
}
@@ -737,8 +737,11 @@ public class TouchInteractionService extends Service implements PluginListener<O
private void reset() {
mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
mGestureState = DEFAULT_STATE;
// By default, use batching of the input events
mInputEventReceiver.setBatchingEnabled(true);
// By default, use batching of the input events, but check receiver before using in the rare
// case that the monitor was disposed before the swipe settled
if (mInputEventReceiver != null) {
mInputEventReceiver.setBatchingEnabled(true);
}
}
private void preloadOverview(boolean fromInit) {
@@ -895,6 +898,12 @@ public class TouchInteractionService extends Service implements PluginListener<O
TouchInteractionServiceProto.Builder serviceProto =
TouchInteractionServiceProto.newBuilder();
serviceProto.setServiceConnected(true);
if (mOverviewComponentObserver != null) {
mOverviewComponentObserver.writeToProto(serviceProto);
}
mConsumer.writeToProto(serviceProto);
proto.setTouchInteractionService(serviceProto);
}
}
@@ -15,21 +15,23 @@
*/
package com.android.quickstep;
import android.graphics.Canvas;
import android.os.Handler;
import android.view.View;
import com.android.systemui.shared.system.WindowCallbacksCompat;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.system.ViewRootImplCompat;
import java.util.function.BooleanSupplier;
import java.util.function.LongConsumer;
/**
* 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);
/** See {@link #postFrameDrawn(View, Runnable, BooleanSupplier)}} */
public static boolean postFrameDrawn(View view, Runnable onFinishRunnable) {
return postFrameDrawn(view, onFinishRunnable, () -> false);
}
/**
@@ -38,37 +40,55 @@ public class ViewUtils {
*
* @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;
public static boolean postFrameDrawn(
View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
return new FrameHandler(view, onFinishRunnable, canceled).schedule();
}
@Override
public void onPostDraw(Canvas canvas) {
// If we were cancelled after this was attached, do not update
// the state.
if (canceled.getAsBoolean()) {
detach();
return;
}
private static class FrameHandler implements LongConsumer {
if (mDeferFrameCount > 0) {
mDeferFrameCount--;
// Workaround, detach and reattach to invalidate the root node for
// another draw
detach();
attach();
view.invalidate();
return;
}
final ViewRootImplCompat mViewRoot;
final Runnable mFinishCallback;
final BooleanSupplier mCancelled;
final Handler mHandler;
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
detach();
int mDeferFrameCount = 1;
FrameHandler(View view, Runnable finishCallback, BooleanSupplier cancelled) {
mViewRoot = new ViewRootImplCompat(view);
mFinishCallback = finishCallback;
mCancelled = cancelled;
mHandler = new Handler();
}
@Override
public void accept(long l) {
Utilities.postAsyncCallback(mHandler, this::onFrame);
}
private void onFrame() {
if (mCancelled.getAsBoolean()) {
return;
}
}.attach();
if (mDeferFrameCount > 0) {
mDeferFrameCount--;
schedule();
return;
}
if (mFinishCallback != null) {
mFinishCallback.run();
}
}
private boolean schedule() {
if (mViewRoot.isValid()) {
mViewRoot.registerRtFrameCallback(this);
mViewRoot.getView().invalidate();
return true;
}
return false;
}
}
}
@@ -4,6 +4,7 @@ import android.view.MotionEvent;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.quickstep.InputConsumer;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -53,4 +54,9 @@ public abstract class DelegateInputConsumer implements InputConsumer {
mDelegate.onMotionEvent(event);
event.recycle();
}
@Override
public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
mDelegate.writeToProtoInternal(inputConsumerProto);
}
}
@@ -51,6 +51,7 @@ import androidx.annotation.UiThread;
import com.android.launcher3.R;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.AbsSwipeUpHandler;
@@ -478,4 +479,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
public boolean allowInterceptByParent() {
return !mPassedPilferInputSlop || mGestureState.hasState(STATE_OVERSCROLL_WINDOW_CREATED);
}
@Override
public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
if (mInteractionHandler != null) {
mInteractionHandler.writeToProto(inputConsumerProto);
}
}
}
@@ -104,7 +104,13 @@ final class AssistantGestureTutorialController extends TutorialController {
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(ASSISTANT_COMPLETE));
() -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(ASSISTANT_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
case ASSISTANT_NOT_STARTED_BAD_ANGLE:
showFeedback(R.string.assistant_gesture_feedback_swipe_not_diagonal);
@@ -130,7 +130,13 @@ final class BackGestureTutorialController extends TutorialController {
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE));
() -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
case BACK_CANCELLED_FROM_LEFT:
showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
@@ -15,8 +15,6 @@
*/
package com.android.quickstep.interaction;
import static com.android.quickstep.interaction.TutorialFragment.KEY_TUTORIAL_TYPE;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
@@ -25,11 +23,14 @@ import android.view.Display;
import android.view.View;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/** Shows the gesture interactive sandbox in full screen mode. */
@@ -37,6 +38,9 @@ public class GestureSandboxActivity extends FragmentActivity {
private static final String LOG_TAG = "GestureSandboxActivity";
private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
private Deque<TutorialType> mTutorialSteps;
private TutorialFragment mFragment;
@Override
@@ -45,7 +49,9 @@ public class GestureSandboxActivity extends FragmentActivity {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.gesture_tutorial_activity);
mFragment = TutorialFragment.newInstance(getTutorialType(getIntent().getExtras()));
Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
mTutorialSteps = getTutorialSteps(args);
mFragment = TutorialFragment.newInstance(mTutorialSteps.pop());
getSupportFragmentManager().beginTransaction()
.add(R.id.gesture_tutorial_fragment_container, mFragment)
.commit();
@@ -72,17 +78,65 @@ public class GestureSandboxActivity extends FragmentActivity {
}
}
private TutorialType getTutorialType(Bundle extras) {
TutorialType defaultType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
@Override
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
super.onSaveInstanceState(savedInstanceState);
}
if (extras == null || !extras.containsKey(KEY_TUTORIAL_TYPE)) {
return defaultType;
/** Returns true iff there aren't anymore tutorial types to display to the user. */
public boolean isTutorialComplete() {
return mTutorialSteps.isEmpty();
}
/**
* Replaces the current TutorialFragment, continuing to the next tutorial step if there is one.
*
* If there is no following step, the tutorial is closed.
*/
public void continueTutorial() {
if (isTutorialComplete()) {
mFragment.closeTutorial();
return;
}
try {
return TutorialType.valueOf(extras.getString(KEY_TUTORIAL_TYPE, ""));
} catch (IllegalArgumentException e) {
return defaultType;
mFragment = TutorialFragment.newInstance(mTutorialSteps.pop());
getSupportFragmentManager().beginTransaction()
.replace(R.id.gesture_tutorial_fragment_container, mFragment)
.runOnCommit(() -> mFragment.onAttachedToWindow())
.commit();
}
private String[] getTutorialStepNames() {
String[] tutorialStepNames = new String[mTutorialSteps.size()];
int i = 0;
for (TutorialType tutorialStep : mTutorialSteps) {
tutorialStepNames[i++] = tutorialStep.name();
}
return tutorialStepNames;
}
private Deque<TutorialType> getTutorialSteps(Bundle extras) {
Deque<TutorialType> defaultSteps = new ArrayDeque<>();
defaultSteps.push(TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
return defaultSteps;
}
String[] tutorialStepNames = extras.getStringArray(KEY_TUTORIAL_STEPS);
if (tutorialStepNames == null) {
return defaultSteps;
}
Deque<TutorialType> tutorialSteps = new ArrayDeque<>();
for (String tutorialStepName : tutorialStepNames) {
tutorialSteps.addLast(TutorialType.valueOf(tutorialStepName));
}
return tutorialSteps;
}
private void hideSystemUI() {
@@ -94,8 +94,13 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
case HOME_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
animateFakeTaskViewHome(finalVelocity, () ->
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE));
animateFakeTaskViewHome(finalVelocity, () -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
}
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
@@ -104,8 +104,13 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
fadeOutFakeTaskView(true, () ->
mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE));
fadeOutFakeTaskView(true, () -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
case HOME_OR_OVERVIEW_CANCELLED:
@@ -54,6 +54,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
fragment = new BackGestureTutorialFragment();
tutorialType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
}
Bundle args = new Bundle();
args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
fragment.setArguments(args);
@@ -197,6 +198,20 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
return mHandCoachingAnimation;
}
void continueTutorial() {
if (!(getContext() instanceof GestureSandboxActivity)) {
closeTutorial();
return;
}
GestureSandboxActivity gestureSandboxActivity = (GestureSandboxActivity) getContext();
if (gestureSandboxActivity == null) {
closeTutorial();
return;
}
gestureSandboxActivity.continueTutorial();
}
void closeTutorial() {
FragmentActivity activity = getActivity();
if (activity != null) {
@@ -207,4 +222,13 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
void startSystemNavigationSetting() {
startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
}
boolean isTutorialComplete() {
if (!(getContext() instanceof GestureSandboxActivity)) {
return true;
}
GestureSandboxActivity gestureSandboxActivity = (GestureSandboxActivity) getContext();
return gestureSandboxActivity == null || gestureSandboxActivity.isTutorialComplete();
}
}
@@ -23,6 +23,7 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static com.android.launcher3.Utilities.newContentObserver;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -73,12 +74,9 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
private static final boolean DEBUG = false;
private static final String DELIMITER_DOT = "\\.";
private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
updateAutoRotateSetting();
}
};
private ContentObserver mSystemAutoRotateObserver =
newContentObserver(new Handler(), t -> updateAutoRotateSetting());
@Retention(SOURCE)
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
public @interface SurfaceRotation {}
@@ -541,6 +539,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
* @return "MyObject@1234"
*/
private static String extractObjectNameAndAddress(String stringToExtract) {
return stringToExtract.substring(stringToExtract.lastIndexOf(DELIMITER_DOT));
int index = stringToExtract.lastIndexOf(DELIMITER_DOT);
return index >= 0 ? stringToExtract.substring(index) : "";
}
}
@@ -322,7 +322,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mTmpCropRect)
.withCornerRadius(getCurrentCornerRadius());
.withCornerRadius(getCurrentCornerRadius())
.withShadowRadius(params.getShadowRadius());
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
// When relativeLayer = 0, it reverts the surfaces back to the original order.
@@ -57,6 +57,7 @@ public class TransformParams {
private float mProgress;
private float mTargetAlpha;
private float mCornerRadius;
private float mShadowRadius;
private RemoteAnimationTargets mTargetSet;
private SurfaceTransactionApplier mSyncTransactionApplier;
private SurfaceControl mRecentsSurface;
@@ -68,6 +69,7 @@ public class TransformParams {
mProgress = 0;
mTargetAlpha = 1;
mCornerRadius = -1;
mShadowRadius = 0;
}
/**
@@ -90,6 +92,14 @@ public class TransformParams {
return this;
}
/**
* Sets the shadow radius of the transformed window, in pixels.
*/
public TransformParams setShadowRadius(float shadowRadius) {
mShadowRadius = shadowRadius;
return this;
}
/**
* Specifies the alpha of the transformed window. Default is 1.
*/
@@ -197,6 +207,10 @@ public class TransformParams {
return mCornerRadius;
}
public float getShadowRadius() {
return mShadowRadius;
}
public SurfaceControl getRecentsSurface() {
return mRecentsSurface;
}
@@ -65,6 +65,10 @@ public class LiveTileOverlay extends Drawable {
invalidateSelf();
}
public void update(float left, float top, float right, float bottom) {
mCurrentRect.set(left, top, right, bottom);
}
public void setIcon(Drawable icon) {
mIcon = icon;
}
@@ -94,18 +98,16 @@ public class LiveTileOverlay extends Drawable {
@Override
public void draw(Canvas canvas) {
if (mCurrentRect != null) {
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
if (mIcon != null && mIconAnimationProgress > 0f) {
canvas.save();
float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
1f).getInterpolation(mIconAnimationProgress);
canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
canvas.scale(scale, scale);
mIcon.draw(canvas);
canvas.restore();
}
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
if (mIcon != null && mIconAnimationProgress > 0f) {
canvas.save();
float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
1f).getInterpolation(mIconAnimationProgress);
canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
canvas.scale(scale, scale);
mIcon.draw(canvas);
canvas.restore();
}
}
@@ -230,6 +230,7 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
view.setScaleX(scale);
view.setScaleY(scale);
view.mLastComputedTaskPushOutDistance = null;
view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
view.updatePageOffsets();
view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
}
@@ -539,6 +540,9 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == GONE && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
finishRecentsAnimation(true /* toRecents */, null);
}
updateTaskStackListenerState();
}
@@ -873,6 +877,10 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
mLiveTileTaskViewSimulator.setOffsetY(0);
// Reset the live tile rect
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
}
if (mRunningTaskTileHidden) {
setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1292,19 +1300,26 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
}
public void showNextTask() {
TaskView runningTaskView = getRunningTaskView();
final TaskView runningTaskView = getRunningTaskView();
final TaskView targetTask;
if (runningTaskView == null) {
// Launch the first task
if (getTaskViewCount() > 0) {
getTaskViewAt(0).launchTask(true);
targetTask = getTaskViewAt(0);
} else {
return;
}
} else {
if (getNextTaskView() != null) {
getNextTaskView().launchTask(true);
final TaskView nextTask = getNextTaskView();
if (nextTask != null) {
targetTask = nextTask;
} else {
runningTaskView.launchTask(true);
targetTask = runningTaskView;
}
}
targetTask.setEndQuickswitchCuj(true);
targetTask.launchTask(true);
}
public void setRunningTaskIconScaledDown(boolean isScaledDown) {
@@ -1976,6 +1991,7 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
TaskView task = getTaskViewAt(i);
mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
}
mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
}
/**
@@ -2254,6 +2270,10 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
return mLiveTileTaskViewSimulator;
}
public TransformParams getLiveTileParams() {
return mLiveTileParams;
}
// TODO: To be removed in a follow up CL
public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
RecentsAnimationTargets recentsAnimationTargets) {
@@ -2442,7 +2462,7 @@ public abstract class RecentsView<T extends StatefulActivity> extends PagedView
} else {
taskView.getThumbnail().refresh();
}
ViewUtils.postDraw(taskView, onFinishRunnable);
ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
} else {
onFinishRunnable.run();
}
@@ -39,6 +39,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
@@ -65,7 +66,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
@@ -75,6 +75,7 @@ import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
@@ -82,10 +83,12 @@ import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
@@ -175,7 +178,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private float mCurveScale;
private float mFullscreenProgress;
private final FullscreenDrawParams mCurrentFullscreenParams;
private final BaseDraggingActivity mActivity;
private final StatefulActivity mActivity;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
@@ -189,6 +192,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private CancellableTask mThumbnailLoadRequest;
private CancellableTask mIconLoadRequest;
private boolean mEndQuickswitchCuj;
// Order in which the footers appear. Lower order appear below higher order.
public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
private final FooterWrapper[] mFooters = new FooterWrapper[2];
@@ -210,18 +215,31 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
mActivity = StatefulActivity.fromContext(context);
setOnClickListener((view) -> {
if (getTask() == null) {
return;
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (isRunningTask()) {
// TODO: Replace this animation with createRecentsWindowAnimator
createLaunchAnimationForRunningTask().start();
} else {
launchTask(true /* animate */);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
RecentsView recentsView = getRecentsView();
RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
AnimatorSet anim = new AnimatorSet();
TaskViewUtils.composeRecentsLaunchAnimator(
anim, this, targets.apps,
targets.wallpapers, true /* launcherClosing */,
mActivity.getStateManager(), recentsView,
recentsView.getDepthController());
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
recentsView.finishRecentsAnimation(false, null);
}
});
anim.start();
} else {
launchTask(true /* animate */);
}
@@ -801,6 +819,14 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
return false;
}
public boolean isEndQuickswitchCuj() {
return mEndQuickswitchCuj;
}
public void setEndQuickswitchCuj(boolean endQuickswitchCuj) {
mEndQuickswitchCuj = endQuickswitchCuj;
}
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;