Merge "Import translations. DO NOT MERGE" into ub-launcher3-edmonton
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 79d8a61..8632f8b 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <TextView
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/clear_all_button"
     android:layout_width="wrap_content"
@@ -11,6 +10,5 @@
     android:text="@string/recents_clear_all"
     android:textColor="?attr/workspaceTextColor"
     android:background="?android:attr/selectableItemBackground"
-    launcher:layout_ignoreInsets="true"
     android:textSize="14sp"
 />
\ No newline at end of file
diff --git a/quickstep/res/xml/indexable_launcher_prefs.xml b/quickstep/res/xml/indexable_launcher_prefs.xml
index 2655402..30f3100 100644
--- a/quickstep/res/xml/indexable_launcher_prefs.xml
+++ b/quickstep/res/xml/indexable_launcher_prefs.xml
@@ -20,8 +20,14 @@
         android:key="pref_add_icon_to_home"
         android:title="@string/auto_add_shortcuts_label"
         android:summary="@string/auto_add_shortcuts_description"
-        android:defaultValue="true"
-        />
+        android:defaultValue="true"  />
+
+    <SwitchPreference
+        android:key="pref_allowRotation"
+        android:title="@string/allow_rotation_title"
+        android:summary="@string/allow_rotation_desc"
+        android:defaultValue="@bool/allow_rotation"
+        android:persistent="true" />
 
     <ListPreference
         android:key="pref_override_icon_shape"
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index ce17d25..353ed84 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -50,7 +50,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Surface;
@@ -249,7 +248,7 @@
                     mLauncher.getStateManager()
                             .createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
             controller.dispatchOnStart();
-            childStateAnimation = controller.getOriginalTarget();
+            childStateAnimation = controller.getTarget();
             launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
             windowAnimEndListener = new AnimatorListenerAdapter() {
                 @Override
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 52db001..d3b0576 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.os.Looper;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 import android.view.View;
@@ -40,15 +39,16 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.LauncherLayoutListener;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 
 /**
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
@@ -67,19 +67,14 @@
 
     void executeOnWindowAvailable(T activity, Runnable action);
 
-    void executeOnNextDraw(T activity, TaskView targetView, Runnable action);
-
     void onTransitionCancelled(T activity, boolean activityVisible);
 
     int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
 
     void onSwipeUpComplete(T activity);
 
-    void prepareRecentsUI(T activity, boolean activityVisible);
-
-    AnimatorPlaybackController createControllerForVisibleActivity(T activity);
-
-    AnimatorPlaybackController createControllerForHiddenActivity(T activity, int transitionLength);
+    AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
+            Consumer<AnimatorPlaybackController> callback);
 
     ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
 
@@ -131,20 +126,6 @@
         }
 
         @Override
-        public void executeOnNextDraw(Launcher activity, TaskView targetView, Runnable action) {
-            ViewOnDrawExecutor executor = new ViewOnDrawExecutor() {
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                    if (!isCompleted()) {
-                        runAllTasks();
-                    }
-                }
-            };
-            executor.attachTo(activity, targetView, false /* waitForLoadAnimation */);
-            executor.execute(action);
-        }
-
-        @Override
         public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
             LayoutUtils.calculateLauncherTaskSize(context, dp, outRect);
             if (dp.isVerticalBarLayout()) {
@@ -169,7 +150,8 @@
         }
 
         @Override
-        public void prepareRecentsUI(Launcher activity, boolean activityVisible) {
+        public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
+                Consumer<AnimatorPlaybackController> callback) {
             LauncherState startState = activity.getStateManager().getState();
             if (startState.disableRestore) {
                 startState = activity.getStateManager().getRestState();
@@ -185,37 +167,41 @@
                 // Optimization, hide the all apps view to prevent layout while initializing
                 activity.getAppsView().getContentView().setVisibility(View.GONE);
             }
+
+            return (transitionLength) ->
+                    createActivityController(activity, activityVisible, transitionLength, callback);
         }
 
-        @Override
-        public AnimatorPlaybackController createControllerForVisibleActivity(Launcher activity) {
-            DeviceProfile dp = activity.getDeviceProfile();
-            long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-            return activity.getStateManager().createAnimationToNewWorkspace(OVERVIEW, accuracy);
-        }
+        private void createActivityController(Launcher activity, boolean wasVisible,
+                long transitionLength, Consumer<AnimatorPlaybackController> callback) {
+            if (wasVisible) {
+                DeviceProfile dp = activity.getDeviceProfile();
+                long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+                callback.accept(activity.getStateManager()
+                        .createAnimationToNewWorkspace(OVERVIEW, accuracy));
+                return;
+            }
 
-        @Override
-        public AnimatorPlaybackController createControllerForHiddenActivity(
-                Launcher activity, int transitionLength) {
+            if (activity.getDeviceProfile().isVerticalBarLayout()) {
+                return;
+            }
+
             AllAppsTransitionController controller = activity.getAllAppsController();
             AnimatorSet anim = new AnimatorSet();
-            if (activity.getDeviceProfile().isVerticalBarLayout()) {
-                // TODO:
-            } else {
-                float scrollRange = Math.max(controller.getShiftRange(), 1);
-                float progressDelta = (transitionLength / scrollRange);
 
-                float endProgress = OVERVIEW.getVerticalProgress(activity);
-                float startProgress = endProgress + progressDelta;
-                ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
-                        controller, ALL_APPS_PROGRESS, startProgress, endProgress);
-                shiftAnim.setInterpolator(LINEAR);
-                anim.play(shiftAnim);
-            }
+            float scrollRange = Math.max(controller.getShiftRange(), 1);
+            float progressDelta = (transitionLength / scrollRange);
+
+            float endProgress = OVERVIEW.getVerticalProgress(activity);
+            float startProgress = endProgress + progressDelta;
+            ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
+                    controller, ALL_APPS_PROGRESS, startProgress, endProgress);
+            shiftAnim.setInterpolator(LINEAR);
+            anim.play(shiftAnim);
 
             anim.setDuration(transitionLength * 2);
             activity.getStateManager().setCurrentAnimation(anim);
-            return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+            callback.accept(AnimatorPlaybackController.wrap(anim, transitionLength * 2));
         }
 
         @Override
@@ -295,13 +281,6 @@
         }
 
         @Override
-        public void executeOnNextDraw(RecentsActivity activity, TaskView targetView,
-                Runnable action) {
-            // TODO:
-            new Handler(Looper.getMainLooper()).post(action);
-        }
-
-        @Override
         public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
             // TODO:
         }
@@ -324,23 +303,43 @@
         }
 
         @Override
-        public void prepareRecentsUI(RecentsActivity activity, boolean activityVisible) {
-            // TODO:
-        }
+        public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
+                Consumer<AnimatorPlaybackController> callback) {
+            if (activityVisible) {
+                return (transitionLength) -> { };
+            }
 
-        @Override
-        public AnimatorPlaybackController createControllerForVisibleActivity(
-                RecentsActivity activity) {
-            DeviceProfile dp = activity.getDeviceProfile();
-            return createControllerForHiddenActivity(activity, Math.max(dp.widthPx, dp.heightPx));
-        }
+            RecentsViewContainer rv = activity.getOverviewPanelContainer();
+            rv.setContentAlpha(0);
 
-        @Override
-        public AnimatorPlaybackController createControllerForHiddenActivity(
-                RecentsActivity activity, int transitionLength) {
-            // We do not animate anything. Create a empty controller
-            AnimatorSet anim = new AnimatorSet();
-            return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+            return new AnimationFactory() {
+
+                boolean isAnimatingHome = false;
+
+                @Override
+                public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
+                    isAnimatingHome = targets != null && targets.isAnimatingHome();
+                    if (!isAnimatingHome) {
+                        rv.setContentAlpha(1);
+                    }
+                    createActivityController(getSwipeUpDestinationAndLength(
+                            activity.getDeviceProfile(), activity, new Rect()));
+                }
+
+                @Override
+                public void createActivityController(long transitionLength) {
+                    if (!isAnimatingHome) {
+                        return;
+                    }
+
+                    ObjectAnimator anim = ObjectAnimator
+                            .ofFloat(rv, RecentsViewContainer.CONTENT_ALPHA, 0, 1);
+                    anim.setDuration(transitionLength).setInterpolator(LINEAR);
+                    AnimatorSet animatorSet = new AnimatorSet();
+                    animatorSet.play(anim);
+                    callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
+                }
+            };
         }
 
         @Override
@@ -423,4 +422,11 @@
         void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
                 Context context, Handler handler, long duration);
     }
+
+    interface AnimationFactory {
+
+        default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
+
+        void createActivityController(long transitionLength);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 663a005..cac7606 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -26,6 +26,7 @@
         .ACTION_PREFERRED_ACTIVITY_CHANGED;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
@@ -36,6 +37,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ResolveInfo;
+import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.PatternMatcher;
@@ -49,6 +51,7 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.AnimationFactory;
 import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
 import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
 import com.android.quickstep.util.ClipAnimationHelper;
@@ -59,6 +62,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
 
 import java.util.ArrayList;
 
@@ -254,15 +258,17 @@
         private boolean onActivityReady(T activity, Boolean wasVisible) {
             activity.<RecentsView>getOverviewPanel().setCurrentTask(mRunningTaskId);
             AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
-            mHelper.prepareRecentsUI(activity, wasVisible);
+            AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible,
+                    (controller) -> {
+                        controller.dispatchOnStart();
+                        ValueAnimator anim = controller.getAnimationPlayer()
+                                .setDuration(RECENTS_LAUNCH_DURATION);
+                        anim.setInterpolator(FAST_OUT_SLOW_IN);
+                        anim.start();
+                });
+            factory.onRemoteAnimationReceived(null);
             if (wasVisible) {
-                AnimatorPlaybackController controller =
-                        mHelper.createControllerForVisibleActivity(activity);
-                controller.dispatchOnStart();
-                ValueAnimator anim =
-                        controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
-                anim.setInterpolator(FAST_OUT_SLOW_IN);
-                anim.start();
+                factory.createActivityController(RECENTS_LAUNCH_DURATION);
             }
             mActivity = activity;
             return false;
@@ -283,7 +289,6 @@
             RemoteAnimationTargetSet targetSet =
                     new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
 
-
             // Use the top closing app to determine the insets for the animation
             RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
             if (runningTaskTarget == null) {
@@ -313,6 +318,22 @@
             valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
             valueAnimator.addUpdateListener((v) ->
                 clipHelper.applyTransform(targetSet, (float) v.getAnimatedValue()));
+
+            if (targetSet.isAnimatingHome()) {
+                // If we are animating home, fade in the opening targets
+                RemoteAnimationTargetSet openingSet =
+                        new RemoteAnimationTargetSet(targetCompats, MODE_OPENING);
+
+                TransactionCompat transaction = new TransactionCompat();
+                valueAnimator.addUpdateListener((v) -> {
+                    for (RemoteAnimationTargetCompat app : openingSet.apps) {
+                        transaction.setAlpha(app.leash, (float) v.getAnimatedValue());
+                        transaction.show(app.leash);
+                    }
+                    transaction.setEarlyWakeup();
+                    transaction.apply();
+                });
+            }
             anim.play(valueAnimator);
             return anim;
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 551984a..0185ab9 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -51,6 +51,7 @@
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
+import com.android.quickstep.views.RecentsViewContainer;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -65,6 +66,7 @@
     private Handler mUiHandler = new Handler(Looper.getMainLooper());
     private RecentsRootView mRecentsRootView;
     private FallbackRecentsView mFallbackRecentsView;
+    private RecentsViewContainer mOverviewPanelContainer;
 
     private Configuration mOldConfig;
 
@@ -78,6 +80,7 @@
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
+        mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
 
         mRecentsRootView.setup();
 
@@ -153,6 +156,10 @@
         return (T) mFallbackRecentsView;
     }
 
+    public RecentsViewContainer getOverviewPanelContainer() {
+        return mOverviewPanelContainer;
+    }
+
     @Override
     public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
         return null;
@@ -210,6 +217,9 @@
 
     @Override
     protected void onStart() {
+        // Set the alpha to 1 before calling super, as it may get set back to 0 due to
+        // onActivityStart callback.
+        mFallbackRecentsView.setContentAlpha(1);
         super.onStart();
         UiFactory.onStart(this);
         mFallbackRecentsView.resetTaskVisuals();
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 70b0355..7676a70 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -87,7 +87,8 @@
     private int mTaskChangeId;
     private ISystemUiProxy mSystemUiProxy;
     private boolean mClearAssistCacheOnStackChange = true;
-    private final boolean mPreloadTasksInBackground;
+    private final boolean mIsLowRamDevice;
+    private boolean mPreloadTasksInBackground;
     private final AccessibilityManager mAccessibilityManager;
 
     private RecentsModel(Context context) {
@@ -95,7 +96,7 @@
 
         ActivityManager activityManager =
                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-        mPreloadTasksInBackground = !activityManager.isLowRamDevice();
+        mIsLowRamDevice = activityManager.isLowRamDevice();
         mMainThreadExecutor = new MainThreadExecutor();
 
         Resources res = context.getResources();
@@ -160,6 +161,10 @@
         return requestId;
     }
 
+    public void setPreloadTasksInBackground(boolean preloadTasksInBackground) {
+        mPreloadTasksInBackground = preloadTasksInBackground && !mIsLowRamDevice;
+    }
+
     @Override
     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
         mTaskChangeId++;
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 2ebf252..e3f6543 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -101,13 +101,9 @@
         }
     }
 
-    public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener,
-            DeviceProfile.OnDeviceProfileChangeListener, View.OnLayoutChangeListener {
+    public static class SplitScreen extends TaskSystemShortcut {
 
         private Handler mHandler;
-        private RecentsView mRecentsView;
-        private TaskView mTaskView;
-        private BaseDraggingActivity mActivity;
 
         public SplitScreen() {
             super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -125,11 +121,44 @@
             if (!task.isDockable) {
                 return null;
             }
-            mActivity = activity;
-            mRecentsView = activity.getOverviewPanel();
-            mTaskView = taskView;
+            final RecentsView recentsView = activity.getOverviewPanel();
+
             final TaskThumbnailView thumbnailView = taskView.getThumbnail();
             return (v -> {
+                final View.OnLayoutChangeListener onLayoutChangeListener =
+                        new View.OnLayoutChangeListener() {
+                            @Override
+                            public void onLayoutChange(View v, int l, int t, int r, int b,
+                                    int oldL, int oldT, int oldR, int oldB) {
+                                taskView.getRootView().removeOnLayoutChangeListener(this);
+                                recentsView.removeIgnoreResetTask(taskView);
+
+                                // Start animating in the side pages once launcher has been resized
+                                recentsView.dismissTask(taskView, false, false);
+                            }
+                        };
+
+                final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
+                        new DeviceProfile.OnDeviceProfileChangeListener() {
+                            @Override
+                            public void onDeviceProfileChanged(DeviceProfile dp) {
+                                activity.removeOnDeviceProfileChangeListener(this);
+                                if (dp.isMultiWindowMode) {
+                                    taskView.getRootView().addOnLayoutChangeListener(
+                                            onLayoutChangeListener);
+                                }
+                            }
+                        };
+
+                final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        taskView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        WindowManagerWrapper.getInstance().endProlongedAnimations();
+                        return true;
+                    }
+                };
+
                 AbstractFloatingView.closeOpenViews(activity, true,
                         AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
 
@@ -145,15 +174,15 @@
 
                     // Add a device profile change listener to kick off animating the side tasks
                     // once we enter multiwindow mode and relayout
-                    activity.addOnDeviceProfileChangeListener(this);
+                    activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
 
                     final Runnable animStartedListener = () -> {
                         // Hide the task view and wait for the window to be resized
                         // TODO: Consider animating in launcher and do an in-place start activity
                         //       afterwards
-                        mRecentsView.addIgnoreResetTask(mTaskView);
-                        mTaskView.setAlpha(0f);
-                        mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
+                        recentsView.addIgnoreResetTask(taskView);
+                        taskView.setAlpha(0f);
+                        taskView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
                     };
 
                     final int[] position = new int[2];
@@ -179,31 +208,6 @@
                 }
             });
         }
-
-        @Override
-        public boolean onPreDraw() {
-            mTaskView.getViewTreeObserver().removeOnPreDrawListener(this);
-            WindowManagerWrapper.getInstance().endProlongedAnimations();
-            return true;
-        }
-
-        @Override
-        public void onDeviceProfileChanged(DeviceProfile dp) {
-            mActivity.removeOnDeviceProfileChangeListener(this);
-            if (dp.isMultiWindowMode) {
-                mTaskView.getRootView().addOnLayoutChangeListener(this);
-            }
-        }
-
-        @Override
-        public void onLayoutChange(View v, int l, int t, int r, int b,
-                int oldL, int oldT, int oldR, int oldB) {
-            mTaskView.getRootView().removeOnLayoutChangeListener(this);
-            mRecentsView.removeIgnoreResetTask(mTaskView);
-
-            // Start animating in the side pages once launcher has been resized
-            mRecentsView.dismissTask(mTaskView, false, false);
-        }
     }
 
     public static class Pin extends TaskSystemShortcut {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 57f46b8..b89a835 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -173,6 +173,7 @@
         super.onCreate();
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.getInstance(this);
+        mRecentsModel.setPreloadTasksInBackground(true);
         mMainThreadExecutor = new MainThreadExecutor();
         mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 0944a7a..25539aa 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.AnimationFactory;
 import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
 import com.android.quickstep.util.ClipAnimationHelper;
@@ -164,6 +165,7 @@
     private LayoutListener mLayoutListener;
     private RecentsView mRecentsView;
     private QuickScrubController mQuickScrubController;
+    private AnimationFactory mAnimationFactory = (t) -> { };
 
     private Runnable mLauncherDrawnCallback;
 
@@ -212,11 +214,13 @@
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
                 this::launcherFrameDrawn);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
-                this::onGestureStartedWithLauncher);
+                this::notifyGestureStartedAsync);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
                         | STATE_GESTURE_CANCELLED,
                 this::resetStateForAnimationCancel);
 
+        mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
+                this::sendRemoteAnimationsToAnimationFactory);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_SCALED_CONTROLLER_APP,
                 this::resumeLastTask);
@@ -321,14 +325,14 @@
             return;
         }
 
-        mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
+        mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+                mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
         AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
         if (mWasLauncherAlreadyVisible) {
             mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
         } else {
             TraceHelper.beginSection("WTS-init");
-            // TODO: Implement a better animation for fading in
             View rootView = activity.getRootView();
             rootView.setAlpha(0);
             rootView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
@@ -377,9 +381,13 @@
         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
     }
 
+    private void sendRemoteAnimationsToAnimationFactory() {
+        mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
+    }
+
     private void initializeLauncherAnimationController() {
         mLayoutListener.setHandler(this);
-        onLauncherLayoutChanged();
+        buildAnimationController();
 
         final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
         SysuiEventLogger.writeDummyRecentsTransition(transitionDelay);
@@ -418,15 +426,15 @@
     /**
      * Called by {@link #mLayoutListener} when launcher layout changes
      */
-    public void onLauncherLayoutChanged() {
+    public void buildAnimationController() {
         initTransitionEndpoints(mActivity.getDeviceProfile());
+        mAnimationFactory.createActivityController(mTransitionDragLength);
+    }
 
-        if (!mWasLauncherAlreadyVisible) {
-            mLauncherTransitionController = mActivityControlHelper
-                    .createControllerForHiddenActivity(mActivity, mTransitionDragLength);
-            mLauncherTransitionController.dispatchOnStart();
-            mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
-        }
+    private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
+        mLauncherTransitionController = anim;
+        mLauncherTransitionController.dispatchOnStart();
+        mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
     }
 
     @WorkerThread
@@ -497,8 +505,7 @@
             dp.updateInsets(homeContentInsets);
         } else {
             overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
-            // TODO: Workaround for an existing issue where the home content insets are
-            // not valid immediately after rotation, just use the stable insets for now
+            // If we are not in multi-window mode, home insets should be same as system insets.
             Rect insets = new Rect();
             WindowManagerWrapper.getInstance().getStableInsets(insets);
             dp = dp.copy(mContext);
@@ -546,17 +553,6 @@
         }
     }
 
-    private void onGestureStartedWithLauncher() {
-        notifyGestureStartedAsync();
-
-        if (mWasLauncherAlreadyVisible) {
-            mLauncherTransitionController = mActivityControlHelper
-                    .createControllerForVisibleActivity(mActivity);
-            mLauncherTransitionController.dispatchOnStart();
-            mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
-        }
-    }
-
     @WorkerThread
     public void onGestureEnded(float endVelocity) {
         Resources res = mContext.getResources();
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 3d64cc8..a4d3a50 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -66,4 +66,9 @@
         LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
     }
 
+    @Override
+    public boolean shouldUseMultiWindowTaskSizeStrategy() {
+        // Just use the activity task size for multi-window as well.
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 9dd83d2..fb7a850 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -25,8 +25,8 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -111,14 +111,17 @@
         mClipRect.bottom = (int)
                 (mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress));
 
-        mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
-
         TransactionCompat transaction = new TransactionCompat();
         for (RemoteAnimationTargetCompat app : targetSet.apps) {
-            mTmpMatrix.postTranslate(app.position.x, app.position.y);
-            transaction.setMatrix(app.leash, mTmpMatrix)
-                    .setWindowCrop(app.leash, mClipRect);
-            if (app.isNotInRecents) {
+            if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+                mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
+                mTmpMatrix.postTranslate(app.position.x, app.position.y);
+                transaction.setMatrix(app.leash, mTmpMatrix)
+                        .setWindowCrop(app.leash, mClipRect);
+            }
+
+            if (app.isNotInRecents
+                    || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
                 transaction.setAlpha(app.leash, 1 - progress);
             }
             transaction.show(app.leash);
@@ -135,7 +138,7 @@
         }
     }
 
-    public void fromTaskThumbnailView(TaskThumbnailView ttv) {
+    public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
         BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
         BaseDragLayer dl = activity.getDragLayer();
 
@@ -144,7 +147,7 @@
         mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
         mHomeStackBounds.offset(pos[0], pos[1]);
 
-        if (activity.isInMultiWindowModeCompat()) {
+        if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
             // TODO: Fetch multi-window target bounds from system-ui
             DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
             // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
index fc889e5..97d7fb9 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
@@ -24,23 +24,18 @@
  */
 public class RemoteAnimationTargetSet {
 
-    public final boolean allTransparent;
     public final RemoteAnimationTargetCompat[] apps;
 
     public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
-        boolean allTransparent = true;
-
         ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
         if (apps != null) {
             for (RemoteAnimationTargetCompat target : apps) {
                 if (target.mode == targetMode) {
-                    allTransparent &= target.isTranslucent;
                     filteredApps.add(target);
                 }
             }
         }
 
-        this.allTransparent = allTransparent;
         this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
     }
 
@@ -52,4 +47,13 @@
         }
         return null;
     }
+
+    public boolean isAnimatingHome() {
+        for (RemoteAnimationTargetCompat target : apps) {
+            if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
index 0fa0023..e1910a6 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -64,7 +64,7 @@
 
         mThumbnailView = tv.getThumbnail();
         mClipAnimationHelper = new ClipAnimationHelper();
-        mClipAnimationHelper.fromTaskThumbnailView(mThumbnailView);
+        mClipAnimationHelper.fromTaskThumbnailView(mThumbnailView, parent);
     }
 
     public void setProgress(float progress) {
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index ac34d90..c149de5 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -54,7 +54,7 @@
     @Override
     public void setInsets(Rect insets) {
         if (mHandler != null) {
-            mHandler.onLauncherLayoutChanged();
+            mHandler.buildAnimationController();
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 90b4bd2..06e5311 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -138,4 +138,9 @@
         }
         super.onTaskLaunched(success);
     }
+
+    @Override
+    public boolean shouldUseMultiWindowTaskSizeStrategy() {
+        return mActivity.isInMultiWindowModeCompat();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6a2df0b..a7b018a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -170,7 +170,7 @@
     // Keeps track of task views whose visual state should not be reset
     private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
 
-    private RecentsViewContainer mContainerView;
+    private View mClearAllButton;
 
     // Variables for empty state
     private final Drawable mEmptyIcon;
@@ -312,12 +312,15 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        if (DEBUG_SHOW_CLEAR_ALL_BUTTON && mTouchState == TOUCH_STATE_REST && mScroller.isFinished()
-                && getChildCount() != 0
-                && ev.getX() > getChildAt(getChildCount() - 1).getRight() - getScrollX()) {
-            // If nothing is in motion, allow events to the right of the last task to go to the
-            // Clear All button.
-            return false;
+        if (DEBUG_SHOW_CLEAR_ALL_BUTTON && ev.getAction() == MotionEvent.ACTION_DOWN
+                && mTouchState == TOUCH_STATE_REST && mScroller.isFinished()
+                && mClearAllButton.getVisibility() == View.VISIBLE) {
+            mClearAllButton.getHitRect(mTempRect);
+            mTempRect.offset(-getLeft(), -getTop());
+            if (mTempRect.contains((int) ev.getX(), (int) ev.getY())) {
+                // If nothing is in motion, let the Clear All button process the event.
+                return false;
+            }
         }
 
         if (ev.getAction() == MotionEvent.ACTION_UP && mShowEmptyMessage) {
@@ -928,8 +931,8 @@
         mShowEmptyMessage = isEmpty;
         updateEmptyStateUi(hasSizeChanged);
         invalidate();
-        if (mContainerView != null) {
-            mContainerView.onEmptyStateChanged(!DEBUG_SHOW_CLEAR_ALL_BUTTON || mShowEmptyMessage);
+        if (mClearAllButton != null) {
+            updateClearAllButtonVisibility();
         }
     }
 
@@ -943,6 +946,7 @@
         boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
         if (sizeChanged && hasValidSize) {
             mEmptyTextLayout = null;
+            mLastMeasureSize.set(getWidth(), getHeight());
         }
 
         if (!mShowEmptyMessage) return;
@@ -953,7 +957,6 @@
         scrollTo(0, 0);
 
         if (hasValidSize && mEmptyTextLayout == null) {
-            mLastMeasureSize.set(getWidth(), getHeight());
             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
                     mEmptyMessagePaint, availableWidth)
@@ -1095,6 +1098,8 @@
         return mPendingAnimation;
     }
 
+    public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
+
     protected void onTaskLaunched(boolean success) {
         resetTaskVisuals();
     }
@@ -1134,8 +1139,13 @@
                 R.dimen.clear_all_container_width) - getPaddingEnd();
     }
 
-    public void setContainerView(RecentsViewContainer containerView) {
-        mContainerView = containerView;
-        mContainerView.onEmptyStateChanged(!DEBUG_SHOW_CLEAR_ALL_BUTTON || mShowEmptyMessage);
+    private void updateClearAllButtonVisibility() {
+        mClearAllButton.setVisibility(
+                !DEBUG_SHOW_CLEAR_ALL_BUTTON || mShowEmptyMessage ? GONE : VISIBLE);
+    }
+
+    public void setClearAllButton(View clearAllButton) {
+        mClearAllButton = clearAllButton;
+        updateClearAllButtonVisibility();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index ece78c1..988b3ad 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -43,7 +43,7 @@
         });
 
         mRecentsView = (RecentsView) findViewById(R.id.overview_panel);
-        mRecentsView.setContainerView(this);
+        mRecentsView.setClearAllButton(mClearAllButton);
     }
 
     @Override
@@ -56,7 +56,8 @@
                 (mClearAllButton.getMeasuredWidth() - getResources().getDimension(
                         R.dimen.clear_all_container_width)) / 2);
         mClearAllButton.setTranslationY(
-                mTempRect.top + (mTempRect.height() - mClearAllButton.getMeasuredHeight()) / 2);
+                mTempRect.top + (mTempRect.height() - mClearAllButton.getMeasuredHeight()) / 2
+                        - mClearAllButton.getTop());
     }
 
     @Override
@@ -74,8 +75,4 @@
         mRecentsView.setContentAlpha(alpha);
         setVisibility(alpha > 0 ? VISIBLE : GONE);
     }
-
-    public void onEmptyStateChanged(boolean isEmpty) {
-        mClearAllButton.setVisibility(isEmpty ? GONE : VISIBLE);
-    }
 }
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bcb90e3..4fbd806 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -179,6 +179,10 @@
     <string name="msg_disabled_by_admin">Disabled by your admin</string>
 
     <!-- Strings for settings -->
+    <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] -->
+    <string name="allow_rotation_title">Allow Home screen rotation</string>
+    <!-- Text explaining when the home screen will get rotated. [CHAR LIMIT=100] -->
+    <string name="allow_rotation_desc">When phone is rotated</string>
     <!-- Title for Notification dots setting. Tapping this will link to the system Notifications settings screen where the user can turn off notification dots globally. [CHAR LIMIT=50] -->
     <string name="icon_badging_title">Notification dots</string>
     <!-- Text to indicate that the system icon badging setting is on [CHAR LIMIT=100] -->
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 7bb19f3..3bba73a 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -34,8 +34,14 @@
         android:title="@string/auto_add_shortcuts_label"
         android:summary="@string/auto_add_shortcuts_description"
         android:defaultValue="true"
-        android:persistent="true"
-        />
+        android:persistent="true" />
+
+    <SwitchPreference
+        android:key="pref_allowRotation"
+        android:title="@string/allow_rotation_title"
+        android:summary="@string/allow_rotation_desc"
+        android:defaultValue="@bool/allow_rotation"
+        android:persistent="true" />
 
     <ListPreference
         android:key="pref_override_icon_shape"
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index a41edc0..d133472 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -143,6 +143,15 @@
     }
 
     @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+
+        // Needed for activities that auto-enter PiP, which will not trigger a remote animation to
+        // be created
+        clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+    }
+
+    @Override
     protected void onStop() {
         mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE;
         mForceInvisible = 0;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ec0a8ff..5e06d92 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -360,7 +360,7 @@
             dispatchDeviceProfileChanged();
 
             getRootView().dispatchInsets();
-            getStateManager().reapplyState();
+            getStateManager().reapplyState(true /* cancelCurrentAnimation */);
 
             // Recreate touch controllers
             mDragLayer.setup(mDragController);
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index f204c16..ad1456a2 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -1,5 +1,8 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_ROOT_VIEW;
+
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -13,9 +16,6 @@
 
 import com.android.launcher3.util.Themes;
 
-import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_ROOT_VIEW;
-
 public class LauncherRootView extends InsettableFrameLayout {
 
     private final Launcher mLauncher;
@@ -82,7 +82,7 @@
             }
         }
         if (resetState) {
-            mLauncher.getStateManager().reapplyState();
+            mLauncher.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
         }
 
         return true; // I'll take it from here
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index e611af7..d196c37 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -157,6 +157,13 @@
     }
 
     public void reapplyState() {
+        reapplyState(false);
+    }
+
+    public void reapplyState(boolean cancelCurrentAnimation) {
+        if (cancelCurrentAnimation) {
+            cancelAnimation();
+        }
         if (mConfig.mCurrentAnimation == null) {
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(mState);
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index c9bd32b..32c198a 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
+
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -59,6 +62,7 @@
     private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
 
     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+    private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
     private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
 
@@ -123,6 +127,16 @@
                     getPreferenceScreen().removePreference(iconShapeOverride);
                 }
             }
+
+            // Setup allow rotation preference
+            Preference rotationPref = findPreference(ALLOW_ROTATION_PREFERENCE_KEY);
+            if (getResources().getBoolean(R.bool.allow_rotation)) {
+                // Launcher supports rotation by default. No need to show this setting.
+                getPreferenceScreen().removePreference(rotationPref);
+            } else {
+                // Initialize the UI once
+                rotationPref.setDefaultValue(getAllowRotationDefaultValue());
+            }
         }
 
         @Override
@@ -273,9 +287,13 @@
         @Override
         public void onClick(DialogInterface dialogInterface, int i) {
             ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
+            Bundle showFragmentArgs = new Bundle();
+            showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
+
             Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .putExtra(":settings:fragment_args_key", cn.flattenToString());
+                    .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
+                    .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
             getActivity().startActivity(intent);
         }
     }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 087752d..1dba7d6 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -17,6 +17,7 @@
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -52,45 +53,37 @@
     private final long mDuration;
 
     protected final AnimatorSet mAnim;
-    private AnimatorSet mOriginalTarget;
 
     protected float mCurrentFraction;
     private Runnable mEndAction;
 
+    protected boolean mTargetCancelled = false;
+
     protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
         mAnim = anim;
-        mOriginalTarget = mAnim;
         mDuration = duration;
 
         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
         mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
         mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
         mAnimationPlayer.addUpdateListener(this);
+
+        mAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mTargetCancelled = true;
+            }
+        });
     }
 
     public AnimatorSet getTarget() {
         return mAnim;
     }
 
-    public void setOriginalTarget(AnimatorSet anim) {
-        mOriginalTarget = anim;
-    }
-
-    public AnimatorSet getOriginalTarget() {
-        return mOriginalTarget;
-    }
-
     public long getDuration() {
         return mDuration;
     }
 
-    public AnimatorPlaybackController cloneFor(AnimatorSet anim) {
-        AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(anim, mDuration);
-        controller.setOriginalTarget(mOriginalTarget);
-        controller.setPlayFraction(mCurrentFraction);
-        return controller;
-    }
-
     /**
      * Starts playing the animation forward from current position.
      */
@@ -206,6 +199,11 @@
         @Override
         public void setPlayFraction(float fraction) {
             mCurrentFraction = fraction;
+            // Let the animator report the progress but don't apply the progress to child
+            // animations if it has been cancelled.
+            if (mTargetCancelled) {
+                return;
+            }
             long playPos = clampDuration(fraction);
             for (ValueAnimator anim : mChildAnimations) {
                 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index 680c020..81f3f90 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -80,6 +80,7 @@
     private final float[] mLeftBorder;
     private final float[] mRightBorder;
     private final Rect mBounds;
+    private final Path mShapePath;
     private final Matrix mMatrix;
 
     private final Paint mPaintIcon;
@@ -116,6 +117,7 @@
         mPaintMaskShapeOutline.setColor(Color.BLACK);
         mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
 
+        mShapePath = new Path();
         mMatrix = new Matrix();
         mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
     }
@@ -146,13 +148,13 @@
         mMatrix.reset();
         mMatrix.setScale(mBounds.width(), mBounds.height());
         mMatrix.postTranslate(mBounds.left, mBounds.top);
-        maskPath.transform(mMatrix);
+        maskPath.transform(mMatrix, mShapePath);
 
         // XOR operation
-        mCanvasARGB.drawPath(maskPath, mPaintMaskShape);
+        mCanvasARGB.drawPath(mShapePath, mPaintMaskShape);
 
         // DST_OUT operation around the mask path outline
-        mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);
+        mCanvasARGB.drawPath(mShapePath, mPaintMaskShapeOutline);
 
         // Check if the result is almost transparent
         return isTransparentBitmap(mBitmapARGB);
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 8f83648..0036bb9 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -18,28 +18,43 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
-import static android.provider.Settings.System.getUriFor;
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+
+import static com.android.launcher3.Utilities.ATLEAST_NOUGAT;
 
 import android.app.Activity;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Resources;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 
 /**
  * Utility class to manage launcher rotation
  */
-public class RotationHelper extends ContentObserver {
+public class RotationHelper implements OnSharedPreferenceChangeListener {
+
+    public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
+
+    public static boolean getAllowRotationDefaultValue() {
+        if (ATLEAST_NOUGAT) {
+            // If the device was scaled, used the original dimensions to determine if rotation
+            // is allowed of not.
+            Resources res = Resources.getSystem();
+            int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
+                    * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
+            return originalSmallestWidth >= 600;
+        }
+        return false;
+    }
 
     public static final int REQUEST_NONE = 0;
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
     private final Activity mActivity;
-    private final ContentResolver mCr;
+    private final SharedPreferences mPrefs;
 
     private final boolean mIgnoreAutoRotateSettings;
     private boolean mAutoRotateEnabled;
@@ -60,23 +75,24 @@
     private int mLastActivityFlags = -1;
 
     public RotationHelper(Activity activity) {
-        super(new Handler());
         mActivity = activity;
 
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = mActivity.getResources().getBoolean(R.bool.allow_rotation);
         if (!mIgnoreAutoRotateSettings) {
-            mCr = mActivity.getContentResolver();
-            mCr.registerContentObserver(getUriFor(ACCELEROMETER_ROTATION), false, this);
-            mAutoRotateEnabled = Settings.System.getInt(mCr, ACCELEROMETER_ROTATION, 1) == 1;
+            mPrefs = Utilities.getPrefs(mActivity);
+            mPrefs.registerOnSharedPreferenceChangeListener(this);
+            mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+                    getAllowRotationDefaultValue());
         } else {
-            mCr = null;
+            mPrefs = null;
         }
     }
 
     @Override
-    public void onChange(boolean selfChange) {
-        mAutoRotateEnabled = Settings.System.getInt(mCr, ACCELEROMETER_ROTATION, 1) == 1;
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+                getAllowRotationDefaultValue());
         notifyChange();
     }
 
@@ -104,8 +120,8 @@
     public void destroy() {
         if (!mDestroyed) {
             mDestroyed = true;
-            if (mCr != null) {
-                mCr.unregisterContentObserver(this);
+            if (mPrefs != null) {
+                mPrefs.unregisterOnSharedPreferenceChangeListener(this);
             }
         }
     }
@@ -121,19 +137,17 @@
                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
         } else if (mCurrentStateRequest == REQUEST_LOCK) {
             activityFlags = SCREEN_ORIENTATION_LOCKED;
-        } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE) {
+        } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
+                || mAutoRotateEnabled) {
             activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
-        } else if (mAutoRotateEnabled) {
-            // If auto rotation is on, lock to device orientation
-            activityFlags = SCREEN_ORIENTATION_NOSENSOR;
         } else {
             // If auto rotation is off, allow rotation on the activity, in case the user is using
             // forced rotation.
-            activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
+            activityFlags = SCREEN_ORIENTATION_NOSENSOR;
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
-            mActivity.setRequestedOrientation(mLastActivityFlags);
+            mActivity.setRequestedOrientation(activityFlags);
         }
     }
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index f1195ed..4c7ce1f 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -279,7 +279,7 @@
 
     @Override
     public void onAnimationCancel(Animator animation) {
-        if (mCurrentAnimation != null && animation == mCurrentAnimation.getOriginalTarget()) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
             Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
             clearState();
         }