summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--quickstep/libs/sysui_shared.jarbin103039 -> 110447 bytes
-rw-r--r--quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java42
-rw-r--r--quickstep/src/com/android/quickstep/LauncherLayoutListener.java77
-rw-r--r--quickstep/src/com/android/quickstep/MultiStateCallback.java4
-rw-r--r--quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java26
-rw-r--r--quickstep/src/com/android/quickstep/RecentsView.java10
-rw-r--r--quickstep/src/com/android/quickstep/RemoteRunnable.java33
-rw-r--r--quickstep/src/com/android/quickstep/TouchInteractionService.java154
-rw-r--r--quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java541
-rw-r--r--src/com/android/launcher3/Launcher.java4
-rw-r--r--src/com/android/launcher3/dragndrop/BaseItemDragListener.java6
-rw-r--r--src/com/android/launcher3/states/InternalStateHandler.java56
12 files changed, 886 insertions, 67 deletions
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 85b40d031..420ecef1a 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
new file mode 100644
index 000000000..0551938c1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.quickstep.TouchInteractionService.InteractionType;
+
+public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
+
+ protected Runnable mGestureEndCallback;
+
+ public void setGestureEndCallback(Runnable gestureEndCallback) {
+ mGestureEndCallback = gestureEndCallback;
+ }
+
+ public void reset() {}
+
+ public abstract void onGestureStarted();
+
+ public abstract void onGestureEnded(float endVelocity);
+
+ public abstract void updateInteractionType(@InteractionType int interactionType);
+
+ public abstract void onQuickScrubEnd();
+
+ public abstract void onQuickScrubProgress(float progress);
+
+ public abstract void updateDisplacement(float displacement);
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/LauncherLayoutListener.java
new file mode 100644
index 000000000..285434297
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherLayoutListener.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.graphics.Rect;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+
+/**
+ * Floating view which shows the task snapshot allowing it to be dragged and placed.
+ */
+public class LauncherLayoutListener extends AbstractFloatingView implements Insettable {
+
+ private final Launcher mLauncher;
+ private final WindowTransformSwipeHandler mHandler;
+
+ public LauncherLayoutListener(Launcher launcher, WindowTransformSwipeHandler handler) {
+ super(launcher, null);
+ mLauncher = launcher;
+ mHandler = handler;
+ setVisibility(INVISIBLE);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ requestLayout();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mHandler.onLauncherLayoutChanged();
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ // We dont suupport animate.
+ mLauncher.getDragLayer().removeView(this);
+ mHandler.layoutListenerClosed();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(1, 1);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // We should probably log the weather
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index cca27299b..7a741764f 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -55,4 +55,8 @@ public class MultiStateCallback {
public void addCallback(int stateMask, Runnable callback) {
mCallbacks.put(stateMask, callback);
}
+
+ public int getState() {
+ return mState;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index b295df0f9..944804ba9 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -41,6 +41,7 @@ import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
+import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -48,7 +49,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.states.InternalStateHandler;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.TouchInteractionService.InteractionType;
@@ -59,7 +59,8 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
@TargetApi(Build.VERSION_CODES.O)
-public class NavBarSwipeInteractionHandler extends InternalStateHandler {
+public class NavBarSwipeInteractionHandler extends BaseSwipeInteractionHandler implements
+ OnResumeCallback {
private static final int STATE_LAUNCHER_READY = 1 << 0;
private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
@@ -187,7 +188,8 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler {
}
@Override
- protected void init(Launcher launcher, boolean alreadyOnHome) {
+ protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+ launcher.setOnResumeCallback(this);
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
mRecentsView.showTask(mRunningTask);
@@ -212,9 +214,9 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler {
mLauncher.getAppsView().setVisibility(View.GONE);
}
TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
+ return false;
}
-
public void updateInteractionType(@InteractionType int interactionType) {
Preconditions.assertUIThread();
if (mInteractionType != INTERACTION_NORMAL) {
@@ -288,8 +290,11 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler {
? mHotseat.getWidth() : mHotseat.getHeight();
}
+ @Override
+ public void onGestureStarted() { }
+
@UiThread
- public void endTouch(float endVelocity) {
+ public void onGestureEnded(float endVelocity) {
if (mTouchEndHandled) {
return;
}
@@ -349,13 +354,24 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler {
ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
}
+ public void reset() {
+ mCurrentShift.cancelAnimation();
+ if (mGestureEndCallback != null) {
+ mGestureEndCallback.run();
+ }
+ }
+
private void cleanupLauncher() {
+ reset();
+
// TODO: These should be done as part of ActivityOptions#OnAnimationStarted
mLauncher.getStateManager().reapplyState();
mLauncher.setOnResumeCallback(() -> mDragView.close(false));
}
private void onAnimationToLauncherComplete() {
+ reset();
+
mDragView.close(false);
View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage());
if (currentRecentsPage instanceof TaskView) {
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index b35d31bfe..6937c1fc2 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -128,6 +128,16 @@ public class RecentsView extends PagedView implements Insettable {
mScrollState.isRtl = mIsRtl;
}
+ public void updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+ for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+ final TaskView taskView = (TaskView) getChildAt(i);
+ if (taskView.getTask().key.id == taskId) {
+ taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
+ return;
+ }
+ }
+ }
+
private void setupLayoutTransition() {
// We want to show layout transitions when pages are deleted, to close the gap.
mLayoutTransition = new LayoutTransition();
diff --git a/quickstep/src/com/android/quickstep/RemoteRunnable.java b/quickstep/src/com/android/quickstep/RemoteRunnable.java
new file mode 100644
index 000000000..ec7cad4ba
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RemoteRunnable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+@FunctionalInterface
+public interface RemoteRunnable {
+
+ void run() throws RemoteException;
+
+ static void executeSafely(RemoteRunnable r) {
+ try {
+ r.run();
+ } catch (final RemoteException e) {
+ Log.e("RemoteRunnable", "Error calling remote method", e);
+ }
+ }
+} \ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 509ffa9dc..c0b12f78b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,8 @@ import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
+import static com.android.quickstep.RemoteRunnable.executeSafely;
+
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
@@ -37,8 +39,8 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
import android.support.annotation.IntDef;
import android.util.Log;
import android.view.Choreographer;
@@ -53,11 +55,16 @@ import android.view.WindowManager;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
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 com.android.systemui.shared.system.WindowManagerWrapper;
import java.lang.annotation.Retention;
@@ -93,19 +100,18 @@ public class TouchInteractionService extends Service {
}
@Override
- public void onBind(ISystemUiProxy iSystemUiProxy) throws RemoteException {
+ public void onBind(ISystemUiProxy iSystemUiProxy) {
mISystemUiProxy = iSystemUiProxy;
}
@Override
public void onQuickSwitch() {
- startTouchTracking(INTERACTION_QUICK_SWITCH);
- mInteractionHandler = null;
+ updateTouchTracking(INTERACTION_QUICK_SWITCH);
}
@Override
public void onQuickScrubStart() {
- startTouchTracking(INTERACTION_QUICK_SCRUB);
+ updateTouchTracking(INTERACTION_QUICK_SCRUB);
sQuickScrubEnabled = true;
}
@@ -113,7 +119,6 @@ public class TouchInteractionService extends Service {
public void onQuickScrubEnd() {
if (mInteractionHandler != null) {
mInteractionHandler.onQuickScrubEnd();
- mInteractionHandler = null;
}
sQuickScrubEnabled = false;
}
@@ -153,9 +158,10 @@ public class TouchInteractionService extends Service {
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
private VelocityTracker mVelocityTracker;
+ private boolean mTouchThresholdCrossed;
private int mTouchSlop;
private float mStartDisplacement;
- private NavBarSwipeInteractionHandler mInteractionHandler;
+ private BaseSwipeInteractionHandler mInteractionHandler;
private int mDisplayRotation;
private Rect mStableInsets = new Rect();
@@ -229,9 +235,10 @@ public class TouchInteractionService extends Service {
}
mVelocityTracker.addMovement(ev);
if (mInteractionHandler != null) {
- mInteractionHandler.endTouch(0);
+ mInteractionHandler.reset();
mInteractionHandler = null;
}
+ mTouchThresholdCrossed = false;
Display display = getSystemService(WindowManager.class).getDefaultDisplay();
mDisplayRotation = display.getRotation();
@@ -266,10 +273,18 @@ public class TouchInteractionService extends Service {
} else if (isNavBarOnLeft()) {
displacement = mDownPos.x - ev.getX(pointerIndex);
}
- if (mInteractionHandler == null) {
- if (Math.abs(displacement) >= mTouchSlop) {
+ if (!mTouchThresholdCrossed) {
+ mTouchThresholdCrossed = Math.abs(displacement) >= mTouchSlop;
+ if (mTouchThresholdCrossed) {
mStartDisplacement = Math.signum(displacement) * mTouchSlop;
- startTouchTracking(INTERACTION_NORMAL);
+
+ startTouchTracking();
+ mInteractionHandler.onGestureStarted();
+
+ // Notify the system that we have started tracking the event
+ if (mISystemUiProxy != null) {
+ executeSafely(mISystemUiProxy::onRecentsAnimationStarted);
+ }
}
} else {
// Move
@@ -282,7 +297,7 @@ public class TouchInteractionService extends Service {
case ACTION_UP: {
TraceHelper.endSection("TouchInt");
- endInteraction();
+ finishTouchTracking();
mCurrentConsumer = mNoOpTouchConsumer;
break;
}
@@ -297,55 +312,92 @@ public class TouchInteractionService extends Service {
return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
}
-
- private void startTouchTracking(@InteractionType int interactionType) {
- if (isInteractionQuick(interactionType)) {
- // TODO: Send action cancel if its the Launcher consumer
- }
- if (mInteractionHandler != null) {
- final NavBarSwipeInteractionHandler handler = mInteractionHandler;
- mMainThreadExecutor.execute(() -> handler.updateInteractionType(interactionType));
- return;
+ /**
+ * Called when the gesture has started.
+ */
+ private void startTouchTracking() {
+ if (Utilities.getPrefs(this).getBoolean("pref_use_screenshot_animation", true)) {
+ // Create the shared handler
+ final NavBarSwipeInteractionHandler handler =
+ new NavBarSwipeInteractionHandler(mRunningTask, this, INTERACTION_NORMAL);
+
+ TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
+
+ // Start the recents activity on a background thread
+ BackgroundExecutor.get().submit(() -> {
+ // Get the snap shot before
+ handler.setTaskSnapshot(getCurrentTaskSnapshot());
+
+ // Start the launcher activity with our custom handler
+ Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
+ startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
+ TraceHelper.partitionSection("TouchInt", "Home started");
+ });
+
+ // Preload the plan
+ mRecentsModel.loadTasks(mRunningTask.id, null);
+ mInteractionHandler = handler;
+ mInteractionHandler.setGestureEndCallback(() -> mInteractionHandler = null);
+ } else {
+
+ // Create the shared handler
+ final WindowTransformSwipeHandler handler =
+ new WindowTransformSwipeHandler(mRunningTask, this);
+
+ BackgroundExecutor.get().submit(() -> {
+ ActivityManagerWrapper.getInstance().startRecentsActivity(mHomeIntent,
+ new AssistDataReceiver() {
+ @Override
+ public void onHandleAssistData(Bundle bundle) {
+ // Pass to AIAI
+ }
+ },
+ new RecentsAnimationListener() {
+ public void onAnimationStart(
+ RecentsAnimationControllerCompat controller,
+ RemoteAnimationTargetCompat[] apps) {
+ if (mInteractionHandler == handler) {
+ handler.setRecentsAnimation(controller, apps);
+
+ } else {
+ controller.finish(false /* toHome */);
+ }
+ }
+
+ public void onAnimationCanceled() {
+ if (mInteractionHandler == handler) {
+ handler.setRecentsAnimation(null, null);
+ }
+ }
+ }, null, null);
+ });
+
+ // Preload the plan
+ mRecentsModel.loadTasks(mRunningTask.id, null);
+ mInteractionHandler = handler;
+ mInteractionHandler.initWhenReady();
+ mInteractionHandler.setGestureEndCallback(() -> mInteractionHandler = null);
}
+ }
- // Create the shared handler
- final NavBarSwipeInteractionHandler handler =
- new NavBarSwipeInteractionHandler(mRunningTask, this, interactionType);
-
- TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
-
- // Start the recents activity on a background thread
- BackgroundExecutor.get().submit(() -> {
- // Get the snap shot before
- handler.setTaskSnapshot(getCurrentTaskSnapshot());
-
- // Start the launcher activity with our custom handler
- Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
- startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
- TraceHelper.partitionSection("TouchInt", "Home started");
-
- /*
- ActivityManagerWrapper.getInstance().startRecentsActivity(null, options,
- ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(),
- null, null);
- */
- });
-
- // Preload the plan
- mRecentsModel.loadTasks(mRunningTask.id, null);
- mInteractionHandler = handler;
+ private void updateTouchTracking(@InteractionType int interactionType) {
+ final BaseSwipeInteractionHandler handler = mInteractionHandler;
+ mMainThreadExecutor.execute(() -> handler.updateInteractionType(interactionType));
}
- private void endInteraction() {
- if (mInteractionHandler != null) {
+ /**
+ * Called when the gesture has ended. Does not correlate to the completion of the interaction as
+ * the animation can still be running.
+ */
+ private void finishTouchTracking() {
+ if (mTouchThresholdCrossed) {
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocity = isNavBarOnRight() ? mVelocityTracker.getXVelocity(mActivePointerId)
: isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
: mVelocityTracker.getYVelocity(mActivePointerId);
- mInteractionHandler.endTouch(velocity);
- mInteractionHandler = null;
+ mInteractionHandler.onGestureEnded(velocity);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
new file mode 100644
index 000000000..e9b22a25e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TouchInteractionService.INTERACTION_NORMAL;
+import static com.android.quickstep.TouchInteractionService.INTERACTION_QUICK_SCRUB;
+import static com.android.quickstep.TouchInteractionService.INTERACTION_QUICK_SWITCH;
+import static com.android.quickstep.TouchInteractionService.isInteractionQuick;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.UserHandle;
+import android.support.annotation.UiThread;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.TouchInteractionService.InteractionType;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+@TargetApi(Build.VERSION_CODES.O)
+public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
+
+ // Launcher UI related states
+ private static final int STATE_LAUNCHER_READY = 1 << 0;
+ private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 1;
+
+ // Internal initialization states
+ private static final int STATE_APP_CONTROLLER_RECEIVED = 1 << 2;
+
+ // Interaction finish states
+ private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 3;
+ private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 4;
+
+ private static final int LAUNCHER_UI_STATES =
+ STATE_LAUNCHER_READY | STATE_ACTIVITY_MULTIPLIER_COMPLETE;
+
+ private static final long MAX_SWIPE_DURATION = 200;
+ private static final long MIN_SWIPE_DURATION = 80;
+ private static final int QUICK_SWITCH_SNAP_DURATION = 120;
+
+ private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+
+ private final Rect mStableInsets = new Rect();
+ private final Rect mSourceRect = new Rect();
+ private final Rect mTargetRect = new Rect();
+ private final Rect mCurrentRect = new Rect();
+ private final Rect mClipRect = new Rect();
+ private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
+ private DeviceProfile mDp;
+ private int mTransitionDragLength;
+
+ // Shift in the range of [0, 1].
+ // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+ // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+ // visible.
+ private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+ private final Task mRunningTask;
+ private final Context mContext;
+
+ private MultiStateCallback mStateCallback;
+ private boolean mControllerStateAnimation;
+ private AnimatorPlaybackController mLauncherTransitionController;
+
+ private Launcher mLauncher;
+ private LauncherLayoutListener mLauncherLayoutListener;
+ private RecentsView mRecentsView;
+ private QuickScrubController mQuickScrubController;
+
+ private boolean mWasLauncherAlreadyVisible;
+
+ private float mCurrentDisplacement;
+
+ private @InteractionType int mInteractionType = INTERACTION_NORMAL;
+ private boolean mStartedQuickScrubFromHome;
+
+ private RecentsAnimationControllerCompat mRecentsAnimationController;
+ private RemoteAnimationTargetCompat[] mRecentsAnimationApps;
+ private boolean mRecentsAnimationInputConsumerEnabled;
+ private Matrix mTmpMatrix = new Matrix();
+
+ private final InputConsumerController mInputConsumerController;
+ private final InputConsumerController.TouchListener mInputConsumerTouchListener =
+ (ev) -> {
+ if (ev.getActionMasked() == ACTION_UP) {
+ onGestureInterruptEnd();
+ }
+ return true;
+ };
+
+ WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) {
+ // TODO: We need a better way for this
+ TaskKey taskKey = new TaskKey(runningTaskInfo.id, 0, null, UserHandle.myUserId(), 0);
+ mRunningTask = new Task(taskKey, null, null, "", "", Color.BLACK, Color.BLACK,
+ true, false, false, false, null, 0, null, false);
+ mContext = context;
+ mInputConsumerController = InputConsumerController.getRecentsAnimationInputConsumer();
+
+
+ WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+
+ DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
+ // TODO: If in multi window mode, dp = dp.getMultiWindowProfile()
+ dp = dp.copy(mContext);
+ // TODO: Use different insets for multi-window mode
+ dp.updateInsets(mStableInsets);
+
+ initTransitionEndpoints(dp);
+ initStateCallbacks();
+ }
+
+ private void initStateCallbacks() {
+ mStateCallback = new MultiStateCallback();
+ mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP | STATE_APP_CONTROLLER_RECEIVED,
+ this::resumeLastTask);
+ mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_RECENTS
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE
+ | STATE_APP_CONTROLLER_RECEIVED,
+ this::switchToScreenshot);
+ mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_RECENTS
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+ this::animateFirstTaskIcon);
+
+ mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
+ this::reset);
+ mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_RECENTS,
+ this::reset);
+ }
+
+ private void initTransitionEndpoints(DeviceProfile dp) {
+ mDp = dp;
+ RecentsView.getPageRect(dp, mContext, mTargetRect);
+ mSourceRect.set(0, 0, dp.widthPx - mStableInsets.left - mStableInsets.right,
+ dp.heightPx - mStableInsets.top - mStableInsets.bottom);
+
+ mTransitionDragLength = dp.hotseatBarSizePx + (dp.isVerticalBarLayout()
+ ? (dp.hotseatBarSidePaddingPx + (dp.isSeascape() ? mStableInsets.left : mStableInsets.right))
+ : mStableInsets.bottom);
+ }
+
+ private long getFadeInDuration() {
+ if (mCurrentShift.getCurrentAnimation() != null) {
+ ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
+ long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
+
+ // TODO: Find a better heuristic
+ return Math.min(MAX_SWIPE_DURATION, Math.max(theirDuration, MIN_SWIPE_DURATION));
+ } else {
+ return MAX_SWIPE_DURATION;
+ }
+ }
+
+ @Override
+ protected boolean init(final Launcher launcher, boolean alreadyOnHome) {
+ if (launcher == mLauncher) {
+ return true;
+ }
+ if (mLauncher != null) {
+ // The launcher may have been recreated as a result of device rotation.
+ int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
+ initStateCallbacks();
+ mStateCallback.setState(oldState);
+ }
+ mLauncher = launcher;
+
+ AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+ mControllerStateAnimation = alreadyOnHome;
+ if (mControllerStateAnimation) {
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+ mLauncherTransitionController = launcher.getStateManager()
+ .createAnimationToNewWorkspace(OVERVIEW, accuracy);
+ mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
+
+ mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+ } else {
+ launcher.getStateManager().goToState(OVERVIEW, false);
+
+ // TODO: Implement a better animation for fading in
+ View rootView = launcher.getRootView();
+ rootView.setAlpha(0);
+ rootView.animate().alpha(1)
+ .setDuration(getFadeInDuration())
+ .withEndAction(() -> mStateCallback.setState(
+ launcher == mLauncher ? STATE_ACTIVITY_MULTIPLIER_COMPLETE : 0));
+ }
+
+ mRecentsView = mLauncher.getOverviewPanel();
+ mRecentsView.showTask(mRunningTask);
+ mWasLauncherAlreadyVisible = alreadyOnHome;
+ mLauncherLayoutListener = new LauncherLayoutListener(mLauncher, this);
+ mLauncher.getDragLayer().addView(mLauncherLayoutListener);
+
+ // Optimization
+ if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+ // All-apps search box is visible in vertical bar layout.
+ mLauncher.getAppsView().setVisibility(View.GONE);
+ }
+
+ onLauncherLayoutChanged();
+ mStateCallback.setState(STATE_LAUNCHER_READY);
+ return true;
+ }
+
+ public void updateInteractionType(@InteractionType int interactionType) {
+ Preconditions.assertUIThread();
+ if (mInteractionType != INTERACTION_NORMAL) {
+ throw new IllegalArgumentException(
+ "Can't change interaction type from " + mInteractionType);
+ }
+ if (!isInteractionQuick(interactionType)) {
+ throw new IllegalArgumentException(
+ "Can't change interaction type to " + interactionType);
+ }
+ mInteractionType = interactionType;
+
+ if (mLauncher != null) {
+ updateUiForQuickScrub();
+ }
+ }
+
+ private void updateUiForQuickScrub() {
+ mStartedQuickScrubFromHome = mWasLauncherAlreadyVisible;
+ mQuickScrubController = mRecentsView.getQuickScrubController();
+ mQuickScrubController.onQuickScrubStart(mStartedQuickScrubFromHome);
+ animateToProgress(1f, MAX_SWIPE_DURATION);
+ if (mStartedQuickScrubFromHome) {
+ mLauncherLayoutListener.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @UiThread
+ public void updateDisplacement(float displacement) {
+ mCurrentDisplacement = displacement;
+
+ float translation = Utilities.boundToRange(-mCurrentDisplacement, 0, mTransitionDragLength);
+ float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+ mCurrentShift.updateValue(shift);
+ }
+
+ /**
+ * Called by {@link #mLauncherLayoutListener} when launcher layout changes
+ */
+ public void onLauncherLayoutChanged() {
+ Hotseat hotseat = mLauncher.getHotseat();
+
+ WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+ initTransitionEndpoints(mLauncher.getDeviceProfile());
+
+ if (!mControllerStateAnimation) {
+ AnimatorSet anim = new AnimatorSet();
+ if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+ mLauncher.getAllAppsController().setProgress(1);
+ ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(mLauncher.getAllAppsController(),
+ AllAppsTransitionController.ALL_APPS_PROGRESS,
+ 1, OVERVIEW.getVerticalProgress(mLauncher));
+ shiftAnim.setInterpolator(LINEAR);
+ anim.play(shiftAnim);
+
+ hotseat.setAlpha(0);
+ ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(hotseat, View.ALPHA, 1);
+ fadeAnim.setInterpolator(LINEAR);
+ anim.play(fadeAnim);
+ } else {
+ hotseat.setTranslationY(mTransitionDragLength);
+ ObjectAnimator hotseatAnim = ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, 0);
+ hotseatAnim.setInterpolator(LINEAR);
+ anim.play(hotseatAnim);
+
+ View scrim = mLauncher.findViewById(R.id.all_apps_scrim);
+ scrim.setTranslationY(mTransitionDragLength);
+ ObjectAnimator scrimAnim = ObjectAnimator.ofFloat(scrim, View.TRANSLATION_Y, 0);
+ scrimAnim.setInterpolator(LINEAR);
+ anim.play(scrimAnim);
+ }
+
+ // TODO: Link this animation to state animation, so that it is cancelled
+ // automatically on state change
+ anim.setDuration(mTransitionDragLength * 2);
+ mLauncherTransitionController =
+ AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2);
+ mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
+ }
+
+ // In case the transition length changed (which should never happen, redo everything
+ updateDisplacement(mCurrentDisplacement);
+ }
+
+ @UiThread
+ private void updateFinalShift() {
+ if (mStartedQuickScrubFromHome) {
+ return;
+ }
+
+ float shift = mCurrentShift.value;
+ mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+ float scale = (float) mCurrentRect.width() / mSourceRect.width();
+ if (mRecentsAnimationApps != null) {
+ mClipRect.left = mSourceRect.left;
+ mClipRect.top = (int) (mStableInsets.top * shift);
+ mClipRect.bottom = (int) (mDp.heightPx - (mStableInsets.bottom * shift));
+ mClipRect.right = mSourceRect.right;
+
+ mTmpMatrix.setScale(scale, scale, 0, 0);
+ mTmpMatrix.postTranslate(mCurrentRect.left - mStableInsets.left * scale * shift,
+ mCurrentRect.top - mStableInsets.top * scale * shift);
+ TransactionCompat transaction = new TransactionCompat();
+ for (RemoteAnimationTargetCompat app : mRecentsAnimationApps) {
+ if (app.mode == MODE_CLOSING) {
+ transaction.setMatrix(app.leash, mTmpMatrix)
+ .setWindowCrop(app.leash, mClipRect)
+ .show(app.leash);
+ }
+ }
+ transaction.apply();
+ }
+
+ if (mLauncherTransitionController != null) {
+ mLauncherTransitionController.setPlayFraction(shift);
+ }
+ }
+
+ public void setRecentsAnimation(RecentsAnimationControllerCompat controller,
+ RemoteAnimationTargetCompat[] apps) {
+ mRecentsAnimationController = controller;
+ if (mRecentsAnimationInputConsumerEnabled) {
+ BackgroundExecutor.get().submit(() ->
+ mRecentsAnimationController.setInputConsumerEnabled(true));
+ }
+ mRecentsAnimationApps = apps;
+ mStateCallback.setState(STATE_APP_CONTROLLER_RECEIVED);
+ }
+
+ public void onGestureStarted() {
+ mInputConsumerController.unregisterInputConsumer();
+ mInputConsumerController.registerInputConsumer();
+ mInputConsumerController.setTouchListener(mInputConsumerTouchListener);
+
+ if (mRecentsAnimationController != null) {
+ BackgroundExecutor.get().submit(() ->
+ mRecentsAnimationController.setInputConsumerEnabled(true));
+ } else {
+ mRecentsAnimationInputConsumerEnabled = true;
+ }
+ }
+
+ @UiThread
+ public void onGestureEnded(float endVelocity) {
+ Resources res = mContext.getResources();
+ float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
+ boolean isFling = Math.abs(endVelocity) > flingThreshold;
+
+ long duration = MAX_SWIPE_DURATION;
+ final float endShift;
+ if (!isFling) {
+ endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
+ } else {
+ endShift = endVelocity < 0 ? 1 : 0;
+ float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
+ if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
+ float distanceToTravel = (endShift - mCurrentShift.value) * mTransitionDragLength;
+
+ // we want the page's snap velocity to approximately match the velocity at
+ // which the user flings, so we scale the duration by a value near to the
+ // derivative of the scroll interpolator at zero, ie. 5.
+ duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
+ }
+ }
+
+ if (endShift == mCurrentShift.value) {
+ mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
+ } else {
+ animateToProgress(endShift, duration);
+ }
+ }
+
+ @UiThread
+ public void onGestureInterruptEnd() {
+ final float endShift = 0;
+ final long duration = MAX_SWIPE_DURATION;
+ animateToProgress(endShift, duration);
+ }
+
+ /** Animates to the given progress, where 0 is the current app and 1 is overview. */
+ private void animateToProgress(float progress, long duration) {
+ ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
+ anim.setInterpolator(Interpolators.SCROLL);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
+ }
+ });
+ anim.start();
+ }
+
+ @UiThread
+ private void resumeLastTask() {
+ if (mRecentsAnimationController != null) {
+ BackgroundExecutor.get().submit(() -> {
+ mRecentsAnimationController.setInputConsumerEnabled(false);
+ mRecentsAnimationController.finish(false /* toHome */);
+ });
+ }
+ }
+
+ public void reset() {
+ mCurrentShift.cancelAnimation();
+
+ if (mGestureEndCallback != null) {
+ mGestureEndCallback.run();
+ }
+ mInputConsumerController.unregisterInputConsumer();
+
+ // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
+ mLauncher.getStateManager().reapplyState();
+ mLauncher.setOnResumeCallback(() -> mLauncherLayoutListener.close(false));
+ mLauncherTransitionController.setPlayFraction(1);
+ clearReference();
+ }
+
+ public void layoutListenerClosed() {
+ if (mControllerStateAnimation) {
+ mLauncherTransitionController.setPlayFraction(1);
+ }
+ }
+
+ private void switchToScreenshot() {
+ mLauncherLayoutListener.close(false);
+ View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage());
+ if (currentRecentsPage instanceof TaskView) {
+ ((TaskView) currentRecentsPage).animateIconToScale(1f);
+ }
+ if (mInteractionType == INTERACTION_QUICK_SWITCH) {
+ for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+ TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
+ // TODO: Match the keys directly
+ if (taskView.getTask().key.id != mRunningTask.key.id) {
+ mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION);
+ taskView.postDelayed(() -> {taskView.launchTask(true);},
+ QUICK_SWITCH_SNAP_DURATION);
+ break;
+ }
+ }
+ } else if (mInteractionType == INTERACTION_QUICK_SCRUB) {
+ if (mQuickScrubController != null) {
+ mQuickScrubController.snapToPageForCurrentQuickScrubSection();
+ }
+ } else {
+ if (mRecentsAnimationController != null) {
+ TransactionCompat transaction = new TransactionCompat();
+ for (RemoteAnimationTargetCompat app : mRecentsAnimationApps) {
+ if (app.mode == MODE_CLOSING) {
+ // Update the screenshot of the task
+ final ThumbnailData thumbnail =
+ mRecentsAnimationController.screenshotTask(app.taskId);
+ mRecentsView.updateThumbnail(app.taskId, thumbnail);
+ }
+ }
+ transaction.apply();
+ BackgroundExecutor.get().submit(() -> {
+ mRecentsAnimationController.setInputConsumerEnabled(false);
+ mRecentsAnimationController.finish(true /* toHome */);
+ });
+ }
+ }
+ }
+
+ private void animateFirstTaskIcon() {
+ View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage());
+ if (currentRecentsPage instanceof TaskView) {
+ ((TaskView) currentRecentsPage).animateIconToScale(1f);
+ }
+ }
+
+ public void onQuickScrubEnd() {
+ if (mQuickScrubController != null) {
+ mQuickScrubController.onQuickScrubEnd();
+ } else {
+ // TODO:
+ }
+ }
+
+ public void onQuickScrubProgress(float progress) {
+ if (mQuickScrubController != null) {
+ mQuickScrubController.onQuickScrubProgress(progress);
+ } else {
+ // TODO:
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a91907d44..c1883b1d1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1174,6 +1174,10 @@ public class Launcher extends BaseActivity
return mAllAppsController;
}
+ public LauncherRootView getRootView() {
+ return (LauncherRootView) mLauncherView;
+ }
+
public DragLayer getDragLayer() {
return mDragLayer;
}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 4629dad3a..9638a75d8 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -75,13 +75,14 @@ public abstract class BaseItemDragListener extends InternalStateHandler implemen
}
@Override
- public void init(Launcher launcher, boolean alreadyOnHome) {
+ public boolean init(Launcher launcher, boolean alreadyOnHome) {
AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
launcher.getDragLayer().setOnDragListener(this);
mLauncher = launcher;
mDragController = launcher.getDragController();
+ return false;
}
@Override
@@ -176,7 +177,4 @@ public abstract class BaseItemDragListener extends InternalStateHandler implemen
mLauncher.getDragLayer().setOnDragListener(null);
}
}
-
- @Override
- public void onLauncherResume() { }
}
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index f084fd256..4c3ef4b2a 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -21,7 +21,11 @@ import android.os.Bundle;
import android.os.IBinder;
import com.android.launcher3.Launcher;
-import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.util.Preconditions;
+
+import java.lang.ref.WeakReference;
/**
* Utility class to sending state handling logic to Launcher from within the same process.
@@ -29,11 +33,17 @@ import com.android.launcher3.Launcher.OnResumeCallback;
* Extending {@link Binder} ensures that the platform maintains a single instance of each object
* which allows this object to safely navigate the system process.
*/
-public abstract class InternalStateHandler extends Binder implements OnResumeCallback {
+public abstract class InternalStateHandler extends Binder {
public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
- protected abstract void init(Launcher launcher, boolean alreadyOnHome);
+ private static WeakReference<InternalStateHandler> sPendingHandler = new WeakReference<>(null);
+
+ /**
+ * Initializes the handler when the launcher is ready.
+ * @return true if the handler wants to stay alive.
+ */
+ protected abstract boolean init(Launcher launcher, boolean alreadyOnHome);
public final Intent addToIntent(Intent intent) {
Bundle extras = new Bundle();
@@ -42,6 +52,29 @@ public abstract class InternalStateHandler extends Binder implements OnResumeCal
return intent;
}
+ public final void initWhenReady() {
+ Preconditions.assertUIThread();
+ sPendingHandler = new WeakReference<>(this);
+ LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+ if (app == null) {
+ return;
+ }
+ Callbacks cb = app.getModel().getCallback();
+ if (!(cb instanceof Launcher)) {
+ return;
+ }
+ Launcher launcher = (Launcher) cb;
+ if (!init(launcher, launcher.isStarted())) {
+ sPendingHandler.clear();
+ }
+ }
+
+ public void clearReference() {
+ if (sPendingHandler.get() == this) {
+ sPendingHandler.clear();
+ }
+ }
+
public static boolean handleCreate(Launcher launcher, Intent intent) {
return handleIntent(launcher, intent, false);
}
@@ -57,12 +90,21 @@ public abstract class InternalStateHandler extends Binder implements OnResumeCal
IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
if (stateBinder instanceof InternalStateHandler) {
InternalStateHandler handler = (InternalStateHandler) stateBinder;
- launcher.setOnResumeCallback(handler);
- handler.init(launcher, alreadyOnHome);
+ if (!handler.init(launcher, alreadyOnHome)) {
+ intent.getExtras().remove(EXTRA_STATE_HANDLER);
+ }
+ result = true;
+ }
+ }
+ if (!result) {
+ InternalStateHandler pendingHandler = sPendingHandler.get();
+ if (pendingHandler != null) {
+ if (!pendingHandler.init(launcher, alreadyOnHome)) {
+ sPendingHandler.clear();
+ }
result = true;
}
- intent.getExtras().remove(EXTRA_STATE_HANDLER);
}
return result;
}
-}
+} \ No newline at end of file