Merge "Separate some elements to set visibility separately" into ub-launcher3-master
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 54a90cf..e2f9ba8 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -22,6 +22,7 @@
android:clipChildren="false"
android:clipToPadding="false"
android:alpha="0.0"
- android:visibility="invisible" >
+ android:visibility="invisible"
+ android:focusableInTouchMode="true" >
</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index b43352a..9e2e5ac 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -28,11 +28,14 @@
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 @@
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 @@
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 @@
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 {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index f60473f..311411f 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -17,25 +17,54 @@
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 @@
@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 @@
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() {
@@ -125,13 +300,35 @@
);
}
- 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);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 12e1a2b..e579205 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -93,4 +93,10 @@
super.onTrimMemory(level);
UiFactory.onTrimMemory(this, level);
}
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ RecentsActivityTracker.onRecentsActivityDestroy(this);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 6a82dc0..5bd606e 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -31,6 +31,7 @@
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 @@
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();
}
}
}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 2d2a483..f0bb367 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -301,6 +301,15 @@
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();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9c0e580..2709f40 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -610,6 +610,16 @@
}
@Override
+ public void onVisibilityAggregated(boolean isVisible) {
+ super.onVisibilityAggregated(isVisible);
+ if (isVisible && !isFocused()) {
+ // Having focus, even in touch mode, keeps us from losing [Alt+]Tab by preventing
+ // switching to keyboard mode.
+ requestFocus();
+ }
+ }
+
+ @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_TAB
&& event.getAction() == KeyEvent.ACTION_DOWN) {