Add code path for the recents animation using window transitions.

Test: Enable in settings, swipe up

Change-Id: I1053f9e519c2f612bd3db0b66cd16ad9a30bfeb4
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 85b40d0..420ecef 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 0000000..0551938
--- /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 0000000..2854342
--- /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 cca2729..7a74176 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -55,4 +55,8 @@
     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 b295df0..944804b 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.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.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.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 @@
     }
 
     @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 @@
             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 @@
                 ? 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 @@
         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 b35d31b..6937c1f 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -128,6 +128,16 @@
         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 0000000..ec7cad4
--- /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 509ffa9..c0b12f7 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_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.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 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 @@
         }
 
         @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 void onQuickScrubEnd() {
             if (mInteractionHandler != null) {
                 mInteractionHandler.onQuickScrubEnd();
-                mInteractionHandler = null;
             }
             sQuickScrubEnabled = false;
         }
@@ -153,9 +158,10 @@
     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 @@
                 }
                 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 @@
                 } 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 @@
             case ACTION_UP: {
                 TraceHelper.endSection("TouchInt");
 
-                endInteraction();
+                finishTouchTracking();
                 mCurrentConsumer = mNoOpTouchConsumer;
                 break;
             }
@@ -297,55 +312,92 @@
         return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
     }
 
+    /**
+     * 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);
 
-    private void startTouchTracking(@InteractionType int interactionType) {
-        if (isInteractionQuick(interactionType)) {
-            // TODO: Send action cancel if its the Launcher consumer
+            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);
         }
-        if (mInteractionHandler != null) {
-            final NavBarSwipeInteractionHandler handler = mInteractionHandler;
-            mMainThreadExecutor.execute(() -> handler.updateInteractionType(interactionType));
-            return;
-        }
-
-        // 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 endInteraction() {
-        if (mInteractionHandler != null) {
+    private void updateTouchTracking(@InteractionType int interactionType) {
+        final BaseSwipeInteractionHandler handler = mInteractionHandler;
+        mMainThreadExecutor.execute(() -> handler.updateInteractionType(interactionType));
+    }
+
+    /**
+     * 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 0000000..e9b22a2
--- /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 a91907d..c1883b1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1174,6 +1174,10 @@
         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 4629dad..9638a75 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -75,13 +75,14 @@
     }
 
     @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 @@
             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 f084fd2..4c3ef4b 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -21,7 +21,11 @@
 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 @@
  * 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 @@
         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 @@
             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;
             }
-            intent.getExtras().remove(EXTRA_STATE_HANDLER);
+        }
+        if (!result) {
+            InternalStateHandler pendingHandler = sPendingHandler.get();
+            if (pendingHandler != null) {
+                if (!pendingHandler.init(launcher, alreadyOnHome)) {
+                    sPendingHandler.clear();
+                }
+                result = true;
+            }
         }
         return result;
     }
-}
+}
\ No newline at end of file