Adding support for single tap & double tap on the recents button

Bug: 75979063
Change-Id: I4dd0595cf94edf77dbedad56f047d86aaf227dac
This commit is contained in:
Sunny Goyal
2018-03-20 14:51:50 -07:00
parent 2e38cf4825
commit 4f37b93e9b
5 changed files with 306 additions and 23 deletions
@@ -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();