Merge "Force finish any pending animations if the insets or orientation change" 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/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/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 5a8ce16..3d64cc8 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.support.annotation.AnyThread;
 import android.util.AttributeSet;
 import android.view.View;
 
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 9276d238..9dd83d2 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -15,15 +15,23 @@
  */
 package com.android.quickstep.util;
 
+import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Rect;
 import android.graphics.RectF;
 
+import com.android.launcher3.BaseDraggingActivity;
+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.TaskThumbnailView;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
 
 /**
  * Utility class to handle window clip animation
@@ -126,4 +134,73 @@
             mTargetRect.offset(offsetX, offsetY);
         }
     }
+
+    public void fromTaskThumbnailView(TaskThumbnailView ttv) {
+        BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
+        BaseDragLayer dl = activity.getDragLayer();
+
+        int[] pos = new int[2];
+        dl.getLocationOnScreen(pos);
+        mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
+        mHomeStackBounds.offset(pos[0], pos[1]);
+
+        if (activity.isInMultiWindowModeCompat()) {
+            // TODO: Fetch multi-window target bounds from system-ui
+            DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
+            // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
+            // account for system insets
+            int taskWidth = fullDp.availableWidthPx;
+            int taskHeight = fullDp.availableHeightPx;
+            int halfDividerSize = activity.getResources()
+                    .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+
+            Rect insets = new Rect();
+            WindowManagerWrapper.getInstance().getStableInsets(insets);
+            if (fullDp.isLandscape) {
+                taskWidth = taskWidth / 2 - halfDividerSize;
+            } else {
+                taskHeight = taskHeight / 2 - halfDividerSize;
+            }
+
+            mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
+            // Align the task to bottom right (probably not true for seascape).
+            mSourceStackBounds.offset(insets.left + fullDp.availableWidthPx - taskWidth,
+                    insets.top + fullDp.availableHeightPx - taskHeight);
+        } else {
+            mSourceStackBounds.set(mHomeStackBounds);
+            mSourceInsets.set(activity.getDeviceProfile().getInsets());
+        }
+
+        Rect targetRect = new Rect();
+        dl.getDescendantRectRelativeToSelf(ttv, targetRect);
+        updateTargetRect(targetRect);
+
+        // Transform the clip relative to the target rect.
+        float scale = mTargetRect.width() / mSourceRect.width();
+        mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
+        mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
+        mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
+        mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
+    }
+
+    public void drawForProgress(TaskThumbnailView ttv, Canvas canvas, float progress) {
+        RectF currentRect;
+        synchronized (mTargetRect) {
+            currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
+        }
+
+        canvas.translate(mSourceStackBounds.left - mHomeStackBounds.left,
+                mSourceStackBounds.top - mHomeStackBounds.top);
+        mTmpMatrix.setRectToRect(mTargetRect, currentRect, ScaleToFit.FILL);
+        canvas.concat(mTmpMatrix);
+        canvas.translate(mTargetRect.left, mTargetRect.top);
+
+        float insetProgress = (1 - progress);
+        ttv.drawOnCanvas(canvas,
+                -mSourceWindowClipInsets.left * insetProgress,
+                -mSourceWindowClipInsets.top * insetProgress,
+                ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
+                ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
+                ttv.getCornerRadius() * progress);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
new file mode 100644
index 0000000..0fa0023
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util;
+
+import android.animation.TimeInterpolator;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.widget.ImageView;
+
+import com.android.launcher3.Utilities;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
+
+public class TaskViewDrawable extends Drawable {
+
+    public static FloatProperty<TaskViewDrawable> PROGRESS =
+            new FloatProperty<TaskViewDrawable>("progress") {
+                @Override
+                public void setValue(TaskViewDrawable taskViewDrawable, float v) {
+                    taskViewDrawable.setProgress(v);
+                }
+
+                @Override
+                public Float get(TaskViewDrawable taskViewDrawable) {
+                    return taskViewDrawable.mProgress;
+                }
+            };
+
+    private static final TimeInterpolator ICON_SIZE_INTERPOLATOR =
+            (t) -> (Math.max(t, 0.3f) - 0.3f) / 0.7f;
+
+    private final RecentsView mParent;
+    private final ImageView mIconView;
+    private final int[] mIconPos;
+
+    private final TaskThumbnailView mThumbnailView;
+
+    private final ClipAnimationHelper mClipAnimationHelper;
+
+    private float mProgress = 1;
+
+    public TaskViewDrawable(TaskView tv, RecentsView parent) {
+        mParent = parent;
+        mIconView = tv.getIconView();
+        mIconPos = new int[2];
+        Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
+
+        mThumbnailView = tv.getThumbnail();
+        mClipAnimationHelper = new ClipAnimationHelper();
+        mClipAnimationHelper.fromTaskThumbnailView(mThumbnailView);
+    }
+
+    public void setProgress(float progress) {
+        mProgress = progress;
+        mParent.invalidate();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        canvas.save();
+        canvas.translate(mParent.getScrollX(), mParent.getScrollY());
+        mClipAnimationHelper.drawForProgress(mThumbnailView, canvas, mProgress);
+        canvas.restore();
+
+        canvas.save();
+        canvas.translate(mIconPos[0], mIconPos[1]);
+        float scale = ICON_SIZE_INTERPOLATOR.getInterpolation(mProgress);
+        canvas.scale(scale, scale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
+        mIconView.draw(canvas);
+        canvas.restore();
+    }
+
+    @Override
+    public void setAlpha(int i) { }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) { }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 280fd46..90b4bd2 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -130,4 +130,12 @@
     protected void getTaskSize(DeviceProfile dp, Rect outRect) {
         LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
     }
+
+    @Override
+    protected void onTaskLaunched(boolean success) {
+        if (success) {
+            mActivity.getStateManager().goToState(NORMAL, false /* animate */);
+        }
+        super.onTaskLaunched(success);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 29d999d..f59ddde 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -66,10 +66,10 @@
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.OverviewCallbacks;
 import com.android.quickstep.QuickScrubController;
-import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.TaskViewDrawable;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.recents.model.Task;
@@ -79,6 +79,7 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 /**
  * A list of recent tasks.
@@ -169,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;
@@ -311,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) {
@@ -522,7 +526,6 @@
         mHasVisibleTaskData.clear();
     }
 
-
     protected abstract void onAllTasksRemoved();
 
     public void reset() {
@@ -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,10 +946,17 @@
         boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
         if (sizeChanged && hasValidSize) {
             mEmptyTextLayout = null;
+            mLastMeasureSize.set(getWidth(), getHeight());
         }
 
-        if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
-            mLastMeasureSize.set(getWidth(), getHeight());
+        if (!mShowEmptyMessage) return;
+
+        // The icon needs to be centered. Need to scoll to horizontal 0 because with Clear-All
+        // space on the right, it's not guaranteed that after deleting all tasks, the horizontal
+        // scroll position will be zero.
+        scrollTo(0, 0);
+
+        if (hasValidSize && mEmptyTextLayout == null) {
             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
                     mEmptyMessagePaint, availableWidth)
@@ -1053,23 +1063,27 @@
             return new PendingAnimation(anim);
         }
 
-        final RecentsAnimationInterpolator recentsInterpolator = tv.getRecentsInterpolator();
-        ValueAnimator targetViewAnim = ValueAnimator.ofFloat(0, 1);
-        targetViewAnim.addUpdateListener((animation) -> {
-            float percent = animation.getAnimatedFraction();
-            TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
-            tv.setScaleX(tw.taskScale);
-            tv.setScaleY(tw.taskScale);
-            tv.setTranslationX(tw.taskX);
-            tv.setTranslationY(tw.taskY);
-        });
-        anim.play(targetViewAnim);
+        tv.setVisibility(INVISIBLE);
+        TaskViewDrawable drawable = new TaskViewDrawable(tv, this);
+        getOverlay().add(drawable);
+
+        ObjectAnimator drawableAnim =
+                ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0);
+        drawableAnim.setInterpolator(LINEAR);
+
+        anim.play(drawableAnim);
         anim.setDuration(duration);
 
+        Consumer<Boolean> onTaskLaunchFinish = (r) -> {
+            onTaskLaunched(r);
+            tv.setVisibility(VISIBLE);
+            getOverlay().remove(drawable);
+        };
+
         mPendingAnimation = new PendingAnimation(anim);
         mPendingAnimation.addEndListener((onEndListener) -> {
             if (onEndListener.isSuccess) {
-                tv.launchTask(false);
+                tv.launchTask(false, onTaskLaunchFinish, getHandler());
                 Task task = tv.getTask();
                 if (task != null) {
                     mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
@@ -1077,13 +1091,17 @@
                             TaskUtils.getComponentKeyForTask(task.key));
                 }
             } else {
-                resetTaskVisuals();
+                onTaskLaunchFinish.accept(false);
             }
             mPendingAnimation = null;
         });
         return mPendingAnimation;
     }
 
+    protected void onTaskLaunched(boolean success) {
+        resetTaskVisuals();
+    }
+
     @Override
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
@@ -1119,8 +1137,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/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 2f7199b..592166d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -143,22 +143,28 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
+        drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
+    }
+
+    public float getCornerRadius() {
+        return mCornerRadius;
+    }
+
+    public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
+            float cornerRadius) {
+        // Always draw the background since the snapshots may be translucent
+        canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
         if (mTask == null) {
             return;
         }
-        int width = getMeasuredWidth();
-        int height = getMeasuredHeight();
-
-        // Always draw the background since the snapshots may be translucent
-        canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius, mBackgroundPaint);
         if (!mTask.isLocked) {
             if (mClipBottom > 0) {
                 canvas.save();
-                canvas.clipRect(0, 0, width, mClipBottom);
-                canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius, mPaint);
+                canvas.clipRect(x, y, width, mClipBottom);
+                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
                 canvas.restore();
             } else {
-                canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius, mPaint);
+                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2c1318c..b32d8dd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -131,6 +131,10 @@
         return mSnapshotView;
     }
 
+    public ImageView getIconView() {
+        return mIconView;
+    }
+
     public void launchTask(boolean animate) {
         launchTask(animate, null, null);
     }
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/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index c9bd32b..7bd6557 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;
@@ -123,6 +126,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
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);
         }
     }
 }