Adding support for single tap & double tap on the recents button
Bug: 75979063 Change-Id: I4dd0595cf94edf77dbedad56f047d86aaf227dac
This commit is contained in:
@@ -28,11 +28,14 @@ import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherInitListener;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
@@ -79,6 +82,13 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
|
||||
void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver,
|
||||
RecentsAnimationListener remoteAnimationListener);
|
||||
|
||||
@UiThread
|
||||
@Nullable
|
||||
RecentsView getVisibleRecentsView();
|
||||
|
||||
@UiThread
|
||||
boolean switchToRecentsIfVisible();
|
||||
|
||||
class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
|
||||
|
||||
@Override
|
||||
@@ -198,6 +208,36 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
|
||||
ActivityManagerWrapper.getInstance().startRecentsActivity(
|
||||
intent, assistDataReceiver, remoteAnimationListener, null, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@UiThread
|
||||
private Launcher getVisibleLaucher() {
|
||||
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
|
||||
if (app == null) {
|
||||
return null;
|
||||
}
|
||||
Launcher launcher = (Launcher) app.getModel().getCallback();
|
||||
return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
|
||||
launcher : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RecentsView getVisibleRecentsView() {
|
||||
Launcher launcher = getVisibleLaucher();
|
||||
return launcher != null && launcher.isInState(OVERVIEW)
|
||||
? launcher.getOverviewPanel() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean switchToRecentsIfVisible() {
|
||||
Launcher launcher = getVisibleLaucher();
|
||||
if (launcher != null) {
|
||||
launcher.getStateManager().goToState(OVERVIEW);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
|
||||
@@ -292,6 +332,21 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
|
||||
new FallbackActivityOptions(remoteAnimationListener), 10000, 10000));
|
||||
context.startActivity(intent, options.toBundle());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RecentsView getVisibleRecentsView() {
|
||||
RecentsActivity activity = RecentsActivityTracker.getCurrentActivity();
|
||||
if (activity != null && activity.hasWindowFocus()) {
|
||||
return activity.getOverviewPanel();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean switchToRecentsIfVisible() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
interface LayoutListener {
|
||||
|
||||
@@ -17,25 +17,54 @@ package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager.RecentTaskInfo;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.MainThreadExecutor;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.states.InternalStateHandler;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
|
||||
import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
|
||||
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
|
||||
import com.android.systemui.shared.recents.view.RecentsTransition;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.AssistDataReceiver;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
|
||||
import com.android.systemui.shared.system.RecentsAnimationListener;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Helper class to handle various atomic commands for switching between Overview.
|
||||
@@ -43,19 +72,39 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class OverviewCommandHelper extends InternalStateHandler {
|
||||
|
||||
private static final int RID_RESET_SWIPE_HANDLER = 0;
|
||||
private static final int RID_CANCEL_CONTROLLER = 1;
|
||||
private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
|
||||
|
||||
private static final long RECENTS_LAUNCH_DURATION = 150;
|
||||
|
||||
private static final String TAG = "OverviewCommandHelper";
|
||||
private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
|
||||
|
||||
private final Context mContext;
|
||||
private final ActivityManagerWrapper mAM;
|
||||
private final RecentsModel mRecentsModel;
|
||||
private final MainThreadExecutor mMainThreadExecutor;
|
||||
|
||||
public final Intent homeIntent;
|
||||
public final ComponentName launcher;
|
||||
|
||||
private final SparseArray<Runnable> mCurrentCommandFinishRunnables = new SparseArray<>();
|
||||
// Monotonically increasing command ids.
|
||||
private int mCurrentCommandId = 0;
|
||||
|
||||
private long mLastToggleTime;
|
||||
private WindowTransformSwipeHandler mWindowTransformSwipeHandler;
|
||||
|
||||
private final Point mWindowSize = new Point();
|
||||
private final Rect mTaskTargetRect = new Rect();
|
||||
private final RectF mTempTaskTargetRect = new RectF();
|
||||
|
||||
public OverviewCommandHelper(Context context) {
|
||||
mContext = context;
|
||||
mAM = ActivityManagerWrapper.getInstance();
|
||||
mMainThreadExecutor = new MainThreadExecutor();
|
||||
mRecentsModel = RecentsModel.getInstance(mContext);
|
||||
|
||||
homeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
@@ -81,26 +130,152 @@ public class OverviewCommandHelper extends InternalStateHandler {
|
||||
initWhenReady();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void addFinishCommand(int requestId, int id, Runnable action) {
|
||||
if (requestId < mCurrentCommandId) {
|
||||
action.run();
|
||||
} else {
|
||||
mCurrentCommandFinishRunnables.put(id, action);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearFinishCommand(int requestId, int id) {
|
||||
if (requestId == mCurrentCommandId) {
|
||||
mCurrentCommandFinishRunnables.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void initSwipeHandler(ActivityControlHelper helper, long time,
|
||||
Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
|
||||
final int commandId = mCurrentCommandId;
|
||||
RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
final WindowTransformSwipeHandler handler =
|
||||
new WindowTransformSwipeHandler(taskInfo, mContext, time, helper);
|
||||
|
||||
// Preload the plan
|
||||
mRecentsModel.loadTasks(taskInfo.id, null);
|
||||
mWindowTransformSwipeHandler = handler;
|
||||
|
||||
mTempTaskTargetRect.setEmpty();
|
||||
handler.setGestureEndCallback(() -> {
|
||||
if (mWindowTransformSwipeHandler == handler) {
|
||||
mWindowTransformSwipeHandler = null;
|
||||
mTempTaskTargetRect.setEmpty();
|
||||
}
|
||||
clearFinishCommand(commandId, RID_RESET_SWIPE_HANDLER);
|
||||
clearFinishCommand(commandId, RID_CANCEL_CONTROLLER);
|
||||
});
|
||||
handler.initWhenReady();
|
||||
addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
|
||||
|
||||
TraceHelper.beginSection(TAG);
|
||||
Runnable startActivity = () -> helper.startRecents(mContext, homeIntent,
|
||||
new AssistDataReceiver() {
|
||||
@Override
|
||||
public void onHandleAssistData(Bundle bundle) {
|
||||
mRecentsModel.preloadAssistData(taskInfo.id, bundle);
|
||||
}
|
||||
},
|
||||
new RecentsAnimationListener() {
|
||||
public void onAnimationStart(
|
||||
RecentsAnimationControllerCompat controller,
|
||||
RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
|
||||
Rect minimizedHomeBounds) {
|
||||
if (mWindowTransformSwipeHandler == handler) {
|
||||
TraceHelper.partitionSection(TAG, "Received");
|
||||
handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
|
||||
minimizedHomeBounds);
|
||||
mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
|
||||
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
addFinishCommand(commandId,
|
||||
RID_CANCEL_CONTROLLER, () -> controller.finish(true));
|
||||
if (commandId == mCurrentCommandId) {
|
||||
onAnimationInitCallback.accept(handler);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
TraceHelper.endSection(TAG, "Finishing no handler");
|
||||
controller.finish(false /* toHome */);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimationCanceled() {
|
||||
TraceHelper.endSection(TAG, "Cancelled: " + handler);
|
||||
if (mWindowTransformSwipeHandler == handler) {
|
||||
handler.onRecentsAnimationCanceled();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// We should almost always get touch-town on background thread. This is an edge case
|
||||
// when the background Choreographer has not yet initialized.
|
||||
BackgroundExecutor.get().submit(startActivity);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void startZoomOutAnim(final WindowTransformSwipeHandler handler) {
|
||||
final int commandId = mCurrentCommandId;
|
||||
ValueAnimator anim = ValueAnimator.ofInt(0, -handler.getTransitionLength());
|
||||
anim.addUpdateListener((a) -> handler.updateDisplacement((Integer) a.getAnimatedValue()));
|
||||
anim.addListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
handler.onGestureEnded(0);
|
||||
clearFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION);
|
||||
}
|
||||
});
|
||||
handler.onGestureStarted();
|
||||
anim.setDuration(RECENTS_LAUNCH_DURATION);
|
||||
anim.setInterpolator(Interpolators.AGGRESSIVE_EASE);
|
||||
anim.start();
|
||||
addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
|
||||
}
|
||||
|
||||
public void onOverviewToggle() {
|
||||
getLauncher().runOnUiThread(() -> {
|
||||
if (isUsingFallbackActivity()) {
|
||||
mContext.startActivity(new Intent(mContext, RecentsActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent
|
||||
.FLAG_ACTIVITY_CLEAR_TASK));
|
||||
return;
|
||||
}
|
||||
long time = SystemClock.elapsedRealtime();
|
||||
mMainThreadExecutor.execute(() -> {
|
||||
long elapsedTime = time - mLastToggleTime;
|
||||
mLastToggleTime = time;
|
||||
|
||||
long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
|
||||
mLastToggleTime = SystemClock.elapsedRealtime();
|
||||
|
||||
if (isOverviewAlmostVisible()) {
|
||||
boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
|
||||
startNonLauncherTask(isQuickTap ? 2 : 1);
|
||||
} else {
|
||||
openRecents();
|
||||
}
|
||||
mCurrentCommandId++;
|
||||
mTempTaskTargetRect.round(mTaskTargetRect);
|
||||
boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
|
||||
int runnableCount = mCurrentCommandFinishRunnables.size();
|
||||
if (runnableCount > 0) {
|
||||
for (int i = 0; i < runnableCount; i++) {
|
||||
mCurrentCommandFinishRunnables.valueAt(i).run();
|
||||
}
|
||||
);
|
||||
mCurrentCommandFinishRunnables.clear();
|
||||
isQuickTap = true;
|
||||
}
|
||||
|
||||
ActivityControlHelper helper = getActivityControlHelper();
|
||||
RecentsView recents = helper.getVisibleRecentsView();
|
||||
if (recents != null) {
|
||||
int childCount = recents.getChildCount();
|
||||
if (childCount != 0) {
|
||||
((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true);
|
||||
}
|
||||
|
||||
// There are not enough tasks. Skip
|
||||
return;
|
||||
}
|
||||
|
||||
if (isQuickTap) {
|
||||
// Focus last task. Start is on background thread so that all ActivityManager calls
|
||||
// are serialized
|
||||
BackgroundExecutor.get().submit(this::startLastTask);
|
||||
return;
|
||||
}
|
||||
if (helper.switchToRecentsIfVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
initSwipeHandler(helper, time, this::startZoomOutAnim);
|
||||
});
|
||||
}
|
||||
|
||||
public void onOverviewShown() {
|
||||
@@ -123,13 +298,35 @@ public class OverviewCommandHelper extends InternalStateHandler {
|
||||
);
|
||||
}
|
||||
|
||||
private void startNonLauncherTask(int backStackCount) {
|
||||
for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
|
||||
backStackCount--;
|
||||
if (backStackCount == 0) {
|
||||
mAM.startActivityFromRecents(rti.id, null);
|
||||
break;
|
||||
@WorkerThread
|
||||
private void startLastTask() {
|
||||
// TODO: This should go through recents model.
|
||||
List<RecentTaskInfo> tasks = mAM.getRecentTasks(2, UserHandle.myUserId());
|
||||
if (tasks.size() > 1) {
|
||||
RecentTaskInfo rti = tasks.get(1);
|
||||
|
||||
final ActivityOptions options;
|
||||
if (!mTaskTargetRect.isEmpty()) {
|
||||
final Rect targetRect = new Rect(mTaskTargetRect);
|
||||
targetRect.offset(Utilities.isRtl(mContext.getResources())
|
||||
? - mTaskTargetRect.width() : mTaskTargetRect.width(), 0);
|
||||
final AppTransitionAnimationSpecCompat specCompat =
|
||||
new AppTransitionAnimationSpecCompat(rti.id, null, targetRect);
|
||||
AppTransitionAnimationSpecsFuture specFuture =
|
||||
new AppTransitionAnimationSpecsFuture(mMainThreadExecutor.getHandler()) {
|
||||
|
||||
@Override
|
||||
public List<AppTransitionAnimationSpecCompat> composeSpecs() {
|
||||
return Collections.singletonList(specCompat);
|
||||
}
|
||||
};
|
||||
options = RecentsTransition.createAspectScaleAnimation(mContext,
|
||||
mMainThreadExecutor.getHandler(), true /* scaleUp */,
|
||||
specFuture, () -> {});
|
||||
} else {
|
||||
options = ActivityOptions.makeBasic();
|
||||
}
|
||||
mAM.startActivityFromRecents(rti.id, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,4 +93,10 @@ public class RecentsActivity extends BaseDraggingActivity {
|
||||
super.onTrimMemory(level);
|
||||
UiFactory.onTrimMemory(this, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
RecentsActivityTracker.onRecentsActivityDestroy(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public class RecentsActivityTracker implements ActivityInitListener {
|
||||
|
||||
private static final Object LOCK = new Object();
|
||||
private static WeakReference<RecentsActivityTracker> sTracker = new WeakReference<>(null);
|
||||
private static WeakReference<RecentsActivity> sCurrentActivity = new WeakReference<>(null);
|
||||
|
||||
private final BiPredicate<RecentsActivity, Boolean> mOnInitListener;
|
||||
|
||||
@@ -60,6 +61,21 @@ public class RecentsActivityTracker implements ActivityInitListener {
|
||||
if (tracker != null && tracker.mOnInitListener.test(activity, false)) {
|
||||
sTracker.clear();
|
||||
}
|
||||
sCurrentActivity = new WeakReference<>(activity);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onRecentsActivityDestroy(RecentsActivity activity) {
|
||||
synchronized (LOCK) {
|
||||
if (sCurrentActivity.get() == activity) {
|
||||
sCurrentActivity.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static RecentsActivity getCurrentActivity() {
|
||||
synchronized (LOCK) {
|
||||
return sCurrentActivity.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
|
||||
mSourceRect.set(scaledTargetRect);
|
||||
}
|
||||
|
||||
public int getTransitionLength() {
|
||||
return mTransitionDragLength;
|
||||
}
|
||||
|
||||
public RectF getTargetRect(Point outWindowSize) {
|
||||
outWindowSize.set(mDp.widthPx, mDp.heightPx);
|
||||
return mInitialTargetRect;
|
||||
}
|
||||
|
||||
private long getFadeInDuration() {
|
||||
if (mCurrentShift.getCurrentAnimation() != null) {
|
||||
ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
|
||||
|
||||
Reference in New Issue
Block a user