Initial changes to creating a fake landscape Launcher UI

Workspace and hotseat are drawn in rotated UI giving the impression that the
device is in Portrait, even though it is in landscape

Bug: 131360075
Change-Id: I29c4068af25fd4dcf7039b9a45886e864a137977
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 61d329b..50f25fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -116,7 +116,7 @@
         } else {
             workspaceView = null;
         }
-        final Rect iconLocation = new Rect();
+        final RectF iconLocation = new RectF();
         boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
         final FloatingIconView floatingView = canUseWorkspaceView
                 ? FloatingIconView.getFloatingIconView(activity, workspaceView,
@@ -138,7 +138,7 @@
                 final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
 
                 if (canUseWorkspaceView) {
-                    return new RectF(iconLocation);
+                    return iconLocation;
                 } else {
                     // Fallback to animate to center of screen.
                     return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
index 10283bf..2ac61c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -52,7 +52,7 @@
 
     private final RecentsView mParent;
     private final View mIconView;
-    private final int[] mIconPos;
+    private final float[] mIconPos;
     private final TaskView mTaskView;
 
     private final TaskThumbnailView mThumbnailView;
@@ -68,7 +68,7 @@
         mParent = parent;
         mTaskView = tv;
         mIconView = tv.getIconView();
-        mIconPos = new int[2];
+        mIconPos = new float[2];
         mIconScale = mIconView.getScaleX();
         Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index a8666f9..e1a115a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -408,7 +408,7 @@
      */
     private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
             Rect windowTargetBounds, boolean toggleVisibility) {
-        Rect bounds = new Rect();
+        RectF bounds = new RectF();
         mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
                 bounds, true /* isOpening */, mFloatingView);
         Rect crop = new Rect();
@@ -423,8 +423,8 @@
         // Scale the app icon to take up the entire screen. This simplifies the math when
         // animating the app window position / scale.
         float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
-        float maxScaleX = smallestSize / (float) bounds.width();
-        float maxScaleY = smallestSize / (float) bounds.height();
+        float maxScaleX = smallestSize / bounds.width();
+        float maxScaleY = smallestSize / bounds.height();
         float scale = Math.max(maxScaleX, maxScaleY);
         float startScale = 1f;
         if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index f300ef7..c84be4d 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -123,12 +123,12 @@
      * @param ev MotionEvent in {@param eventSource}
      */
     public boolean shouldContainerScroll(MotionEvent ev, View eventSource) {
-        int[] point = new int[2];
-        point[0] = (int) ev.getX();
-        point[1] = (int) ev.getY();
+        float[] point = new float[2];
+        point[0] = ev.getX();
+        point[1] = ev.getY();
         Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point);
         // IF the MotionEvent is inside the thumb, container should not be pulled down.
-        if (mScrollbar.shouldBlockIntercept(point[0], point[1])) {
+        if (mScrollbar.shouldBlockIntercept((int) point[0], (int) point[1])) {
             return false;
         }
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3611ad4..8291acc 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -142,7 +142,6 @@
     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mActivity = ActivityContext.lookupContext(context);
-        DeviceProfile grid = mActivity.getDeviceProfile();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
@@ -150,18 +149,24 @@
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
 
         int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
-        int defaultIconSize = grid.iconSizePx;
+        final int defaultIconSize;
         if (display == DISPLAY_WORKSPACE) {
+            DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
+            defaultIconSize = grid.iconSizePx;
         } else if (display == DISPLAY_ALL_APPS) {
+            DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
         } else if (display == DISPLAY_FOLDER) {
+            DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
+        } else {
+            defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
         }
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 9d9a639..fe6bbc0 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -57,12 +57,14 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.lang.annotation.Retention;
@@ -76,7 +78,7 @@
 import androidx.annotation.IntDef;
 import androidx.core.view.ViewCompat;
 
-public class CellLayout extends ViewGroup {
+public class CellLayout extends ViewGroup implements Transposable {
     public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
     public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
 
@@ -182,6 +184,7 @@
     // Related to accessible drag and drop
     private DragAndDropAccessibilityDelegate mTouchHelper;
     private boolean mUseTouchHelper = false;
+    private RotationMode mRotationMode = RotationMode.NORMAL;
 
     public CellLayout(Context context) {
         this(context, null);
@@ -203,7 +206,7 @@
         setClipToPadding(false);
         mActivity = ActivityContext.lookupContext(context);
 
-        DeviceProfile grid = mActivity.getDeviceProfile();
+        DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
 
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
@@ -317,6 +320,24 @@
         }
     }
 
+    public void setRotationMode(RotationMode mode) {
+        if (mRotationMode != mode) {
+            mRotationMode = mode;
+            requestLayout();
+        }
+    }
+
+    @Override
+    public RotationMode getRotationMode() {
+        return mRotationMode;
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        mRotationMode.mapRect(left, top, right, bottom, mTempRect);
+        super.setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
+    }
+
     @Override
     public boolean dispatchHoverEvent(MotionEvent event) {
         // Always attempt to dispatch hover events to accessibility first.
@@ -745,6 +766,14 @@
         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
         int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
         int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
+
+        mShortcutsAndWidgets.setRotation(mRotationMode.surfaceRotation);
+        if (mRotationMode.isTransposed) {
+            int tmp = childWidthSize;
+            childWidthSize = childHeightSize;
+            childHeightSize = tmp;
+        }
+
         if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
             int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
             int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
@@ -787,7 +816,6 @@
         int top = getPaddingTop();
         int bottom = b - t - getPaddingBottom();
 
-        mShortcutsAndWidgets.layout(left, top, right, bottom);
         // Expand the background drawing bounds by the padding baked into the background drawable
         mBackground.getPadding(mTempRect);
         mBackground.setBounds(
@@ -795,6 +823,16 @@
                 top - mTempRect.top - getPaddingTop(),
                 right + mTempRect.right + getPaddingRight(),
                 bottom + mTempRect.bottom + getPaddingBottom());
+
+        if (mRotationMode.isTransposed) {
+            int halfW = mShortcutsAndWidgets.getMeasuredWidth() / 2;
+            int halfH = mShortcutsAndWidgets.getMeasuredHeight() / 2;
+            int cX = (left + right) / 2;
+            int cY = (top + bottom) / 2;
+            mShortcutsAndWidgets.layout(cX - halfW, cY - halfH, cX + halfW, cY + halfH);
+        } else {
+            mShortcutsAndWidgets.layout(left, top, right, bottom);
+        }
     }
 
     /**
@@ -929,7 +967,7 @@
             if (resize) {
                 cellToRect(cellX, cellY, spanX, spanY, r);
                 if (v instanceof LauncherAppWidgetHostView) {
-                    DeviceProfile profile = mActivity.getDeviceProfile();
+                    DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
                     Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
                 }
             } else {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 6a3a26f..c0affb9 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -419,6 +419,10 @@
         updateWorkspacePadding();
     }
 
+    /**
+     * The current device insets. This is generally same as the insets being dispatched to
+     * {@link Insettable} elements, but can differ if the element is using a different profile.
+     */
     public Rect getInsets() {
         return mInsets;
     }
@@ -582,45 +586,6 @@
         }
     }
 
-    /**
-     * Gets an item's location on the home screen. This is useful if the home screen
-     * is animating, otherwise use {@link View#getLocationOnScreen(int[])}.
-     * @param pageDiff The page difference relative to the current page.
-     */
-    public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container,
-            int pageDiff, Rect outBounds) {
-        outBounds.setEmpty();
-        if (container == CONTAINER_HOTSEAT) {
-            final int actualHotseatCellHeight;
-            if (isVerticalBarLayout()) {
-                actualHotseatCellHeight = availableHeightPx / inv.numRows;
-                if (mIsSeascape) {
-                    outBounds.left = mHotseatPadding.left;
-                } else {
-                    outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left;
-                }
-                outBounds.right = outBounds.left + iconSizePx;
-                outBounds.top = mHotseatPadding.top
-                        + actualHotseatCellHeight * (inv.numRows - cellX - 1);
-                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
-            } else {
-                actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx
-                        - hotseatBarTopPaddingPx;
-                outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
-                        + (cellX * getCellSize().x);
-                outBounds.right = outBounds.left + getCellSize().x;
-                outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx;
-                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
-            }
-        } else {
-            outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
-                    + (cellX * getCellSize().x) + (pageDiff * availableWidthPx);
-            outBounds.right = outBounds.left + (getCellSize().x * spanX);
-            outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y);
-            outBounds.bottom = outBounds.top + (getCellSize().y * spanY);
-        }
-    }
-
     private static Context getContext(Context c, int orientation) {
         Configuration context = new Configuration(c.getResources().getConfiguration());
         context.orientation = orientation;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 4da7907..00acdcd 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -26,11 +26,13 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.views.Transposable;
 
-public class Hotseat extends CellLayout implements LogContainerProvider, Insettable {
+public class Hotseat extends CellLayout implements LogContainerProvider, Insettable, Transposable {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
@@ -77,7 +79,8 @@
     @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        DeviceProfile grid = mActivity.getDeviceProfile();
+        DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
+        insets = grid.getInsets();
 
         if (grid.isVerticalBarLayout()) {
             lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -105,4 +108,9 @@
         // Don't let if follow through to workspace
         return true;
     }
+
+    @Override
+    public RotationMode getRotationMode() {
+        return Launcher.getLauncher(getContext()).getRotationMode();
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ddaaa5d..417c5a2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -53,6 +53,7 @@
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -71,6 +72,7 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
@@ -93,6 +95,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -272,6 +275,9 @@
 
     private float mCurrentAssistantVisibility = 0f;
 
+    private DeviceProfile mStableDeviceProfile;
+    private RotationMode mRotationMode = RotationMode.NORMAL;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
@@ -390,6 +396,12 @@
                 }
             }
         });
+
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
+            WindowManager.LayoutParams lp = getWindow().getAttributes();
+            lp.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+            getWindow().setAttributes(lp);
+        }
     }
 
     @Override
@@ -418,6 +430,10 @@
 
     @Override
     protected void reapplyUi() {
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
+            mRotationMode = mStableDeviceProfile == null ? RotationMode.NORMAL :
+                    (mDeviceProfile.isSeascape() ? RotationMode.SEASCAPE : RotationMode.LANDSCAPE);
+        }
         getRootView().dispatchInsets();
         getStateManager().reapplyState(true /* cancelCurrentAnimation */);
     }
@@ -469,8 +485,41 @@
             display.getSize(mwSize);
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
         }
+
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get() && mDeviceProfile.isVerticalBarLayout()
+                && !mDeviceProfile.isMultiWindowMode) {
+            mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
+            mRotationMode = mDeviceProfile.isSeascape()
+                    ? RotationMode.SEASCAPE : RotationMode.LANDSCAPE;
+        } else {
+            mStableDeviceProfile = null;
+            mRotationMode = RotationMode.NORMAL;
+        }
+
         onDeviceProfileInitiated();
-        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
+        mModelWriter = mModel.getWriter(getWallpaperDeviceProfile().isVerticalBarLayout(), true);
+    }
+
+    public void updateInsets(Rect insets) {
+        mDeviceProfile.updateInsets(insets);
+        if (mStableDeviceProfile != null) {
+            mStableDeviceProfile.updateInsets(insets);
+        }
+    }
+
+    @Override
+    public RotationMode getRotationMode() {
+        return mRotationMode;
+    }
+
+    /**
+     * Device profile to be used by UI elements which are shown directly on top of the wallpaper
+     * and whose presentation is tied to the wallpaper (and physical device) and not the activity
+     * configuration.
+     */
+    @Override
+    public DeviceProfile getWallpaperDeviceProfile() {
+        return mStableDeviceProfile == null ? mDeviceProfile : mStableDeviceProfile;
     }
 
     public RotationHelper getRotationHelper() {
@@ -1812,7 +1861,7 @@
         mAppWidgetHost.clearViews();
 
         if (mHotseat != null) {
-            mHotseat.resetLayout(mDeviceProfile.isVerticalBarLayout());
+            mHotseat.resetLayout(getWallpaperDeviceProfile().isVerticalBarLayout());
         }
         TraceHelper.endSection("startBinding");
     }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 8140026..20eec05 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -81,7 +81,7 @@
                 UI_STATE_ROOT_VIEW, drawInsetBar ? FLAG_DARK_NAV : 0);
 
         // Update device profile before notifying th children.
-        mLauncher.getDeviceProfile().updateInsets(insets);
+        mLauncher.updateInsets(insets);
         boolean resetState = !insets.equals(mInsets);
         setInsets(insets);
 
@@ -114,7 +114,7 @@
     }
 
     public void dispatchInsets() {
-        mLauncher.getDeviceProfile().updateInsets(mInsets);
+        mLauncher.updateInsets(mInsets);
         super.setInsets(mInsets);
     }
 
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 30f418d..1bd8263 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -93,7 +93,7 @@
     public void setupLp(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (child instanceof LauncherAppWidgetHostView) {
-            DeviceProfile profile = mActivity.getDeviceProfile();
+            DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
                     profile.appWidgetScale.x, profile.appWidgetScale.y);
         } else {
@@ -108,12 +108,12 @@
 
     public int getCellContentHeight() {
         return Math.min(getMeasuredHeight(),
-                mActivity.getDeviceProfile().getCellHeight(mContainerType));
+                mActivity.getWallpaperDeviceProfile().getCellHeight(mContainerType));
     }
 
     public void measureChild(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        final DeviceProfile profile = mActivity.getDeviceProfile();
+        final DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
 
         if (child instanceof LauncherAppWidgetHostView) {
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 382eedd..26364be 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,9 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.WallpaperManager;
@@ -63,13 +60,12 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.io.Closeable;
@@ -97,7 +93,6 @@
 
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
-    private static final float[] sPoint = new float[2];
     private static final Matrix sMatrix = new Matrix();
     private static final Matrix sInverseMatrix = new Matrix();
 
@@ -143,7 +138,7 @@
      */
     public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
-            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
 
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
@@ -170,37 +165,60 @@
      *         assumption fails, we will need to return a pair of scale factors.
      */
     public static float getDescendantCoordRelativeToAncestor(
-            View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
-        sPoint[0] = coord[0];
-        sPoint[1] = coord[1];
+            View descendant, View ancestor, float[] coord, boolean includeRootScroll) {
+        return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll,
+                false);
+    }
 
+    /**
+     * Given a coordinate relative to the descendant, find the coordinate in a parent view's
+     * coordinates.
+     *
+     * @param descendant The descendant to which the passed coordinate is relative.
+     * @param ancestor The root view to make the coordinates relative to.
+     * @param coord The coordinate that we want mapped.
+     * @param includeRootScroll Whether or not to account for the scroll of the descendant:
+     *          sometimes this is relevant as in a child's coordinates within the descendant.
+     * @param ignoreTransform If true, view transform is ignored
+     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
+     *         assumption fails, we will need to return a pair of scale factors.
+     */
+    public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor,
+            float[] coord, boolean includeRootScroll, boolean ignoreTransform) {
         float scale = 1.0f;
         View v = descendant;
         while(v != ancestor && v != null) {
             // For TextViews, scroll has a meaning which relates to the text position
             // which is very strange... ignore the scroll.
             if (v != descendant || includeRootScroll) {
-                sPoint[0] -= v.getScrollX();
-                sPoint[1] -= v.getScrollY();
+                offsetPoints(coord, -v.getScrollX(), -v.getScrollY());
             }
 
-            v.getMatrix().mapPoints(sPoint);
-            sPoint[0] += v.getLeft();
-            sPoint[1] += v.getTop();
+            if (ignoreTransform) {
+                if (v instanceof Transposable) {
+                    RotationMode m = ((Transposable) v).getRotationMode();
+                    if (m.isTransposed) {
+                        sMatrix.setRotate(m.surfaceRotation, v.getPivotX(), v.getPivotY());
+                        sMatrix.mapPoints(coord);
+                    }
+                }
+            } else {
+                v.getMatrix().mapPoints(coord);
+            }
+            offsetPoints(coord, v.getLeft(), v.getTop());
             scale *= v.getScaleX();
 
             v = (View) v.getParent();
         }
-
-        coord[0] = Math.round(sPoint[0]);
-        coord[1] = Math.round(sPoint[1]);
         return scale;
     }
 
+
     /**
-     * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
+     * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
      */
-    public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) {
+    public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
         sMatrix.reset();
         View v = descendant;
         while(v != root) {
@@ -211,12 +229,23 @@
         }
         sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
         sMatrix.invert(sInverseMatrix);
+        sInverseMatrix.mapPoints(coord);
+    }
 
-        sPoint[0] = coord[0];
-        sPoint[1] = coord[1];
-        sInverseMatrix.mapPoints(sPoint);
-        coord[0] = Math.round(sPoint[0]);
-        coord[1] = Math.round(sPoint[1]);
+    /**
+     * Sets {@param out} to be same as {@param in} by rounding individual values
+     */
+    public static void roundArray(float[] in, int[] out) {
+       for (int i = 0; i < in.length; i++) {
+           out[i] = Math.round(in[i]);
+       }
+    }
+
+    public static void offsetPoints(float[] points, float offsetX, float offsetY) {
+        for (int i = 0; i < points.length; i += 2) {
+            points[i] += offsetX;
+            points[i + 1] += offsetY;
+        }
     }
 
     /**
@@ -569,53 +598,6 @@
         return String.format(Locale.ENGLISH, "%d,%d", x, y);
     }
 
-    /**
-     * Returns the location bounds of a view.
-     * - For DeepShortcutView, we return the bounds of the icon view.
-     * - For BubbleTextView, we return the icon bounds.
-     */
-    public static void getLocationBoundsForView(Launcher launcher, View v, Rect outRect) {
-        final DragLayer dragLayer = launcher.getDragLayer();
-        final boolean isBubbleTextView = v instanceof BubbleTextView;
-        final boolean isFolderIcon = v instanceof FolderIcon;
-        final Rect rect = new Rect();
-
-        // Deep shortcut views have their icon drawn in a separate view.
-        final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
-        if (v instanceof DeepShortcutView) {
-            dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect);
-        } else if (fromDeepShortcutView) {
-            DeepShortcutView view = (DeepShortcutView) v.getParent();
-            dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
-        } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
-                && (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP
-                || ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) {
-            CellLayout pageViewIsOn = ((CellLayout) v.getParent().getParent());
-            int pageNum = launcher.getWorkspace().indexOfChild(pageViewIsOn);
-
-            DeviceProfile dp = launcher.getDeviceProfile();
-            ItemInfo info = ((ItemInfo) v.getTag());
-            dp.getItemLocation(info.cellX, info.cellY, info.spanX, info.spanY,
-                    info.container, pageNum - launcher.getCurrentWorkspaceScreen(), rect);
-        } else {
-            dragLayer.getDescendantRectRelativeToSelf(v, rect);
-        }
-        int viewLocationLeft = rect.left;
-        int viewLocationTop = rect.top;
-
-        if (isBubbleTextView && !fromDeepShortcutView) {
-            ((BubbleTextView) v).getIconBounds(rect);
-        } else if (isFolderIcon) {
-            ((FolderIcon) v).getPreviewBounds(rect);
-        } else {
-            rect.set(0, 0, rect.width(), rect.height());
-        }
-        viewLocationLeft += rect.left;
-        viewLocationTop += rect.top;
-        outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + rect.width(),
-                viewLocationTop + rect.height());
-    }
-
     public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
         try {
             context.unregisterReceiver(receiver);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f1c183b..0f4c42d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -82,6 +82,7 @@
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -175,7 +176,9 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
+    private final Rect mTempRect = new Rect();
     private final int[] mTempXY = new int[2];
+    private final float[] mTempFXY = new float[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
     private final float[] mTempTouchCoordinates = new float[2];
 
@@ -285,18 +288,23 @@
 
     @Override
     public void setInsets(Rect insets) {
-        mInsets.set(insets);
-
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        mMaxDistanceForFolderCreation = grid.isTablet
-                ? 0.75f * grid.iconSizePx
-                : 0.55f * grid.iconSizePx;
+        DeviceProfile stableGrid = mLauncher.getWallpaperDeviceProfile();
+
+        mMaxDistanceForFolderCreation = stableGrid.isTablet
+                ? 0.75f * stableGrid.iconSizePx
+                : 0.55f * stableGrid.iconSizePx;
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
 
-        Rect padding = grid.workspacePadding;
-        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+        Rect padding = stableGrid.workspacePadding;
 
-        if (grid.shouldFadeAdjacentWorkspaceScreens()) {
+        RotationMode rotationMode = mLauncher.getRotationMode();
+
+        rotationMode.mapRect(padding, mTempRect);
+        setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
+        rotationMode.mapRect(insets, mInsets);
+
+        if (mWorkspaceFadeInAdjacentScreens) {
             // In landscape mode the page spacing is set to the default.
             setPageSpacing(grid.edgeMarginPx);
         } else {
@@ -306,11 +314,13 @@
             setPageSpacing(Math.max(grid.edgeMarginPx, padding.left + 1));
         }
 
-        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
-        int paddingBottom = grid.cellLayoutBottomPaddingPx;
+
+        int paddingLeftRight = stableGrid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = stableGrid.cellLayoutBottomPaddingPx;
         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
-            mWorkspaceScreens.valueAt(i)
-                    .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+            CellLayout page = mWorkspaceScreens.valueAt(i);
+            page.setRotationMode(rotationMode);
+            page.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
         }
     }
 
@@ -330,7 +340,7 @@
 
             float scale = 1;
             if (isWidget) {
-                DeviceProfile profile = mLauncher.getDeviceProfile();
+                DeviceProfile profile = mLauncher.getWallpaperDeviceProfile();
                 scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
             }
             size[0] = r.width();
@@ -550,8 +560,10 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
-        int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
+        DeviceProfile grid = mLauncher.getWallpaperDeviceProfile();
+        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = grid.cellLayoutBottomPaddingPx;
+        newScreen.setRotationMode(mLauncher.getRotationMode());
         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
 
         mWorkspaceScreens.put(screenId, newScreen);
@@ -1930,18 +1942,9 @@
         if (child == null) {
             return;
         }
+
         ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
-
-        // Use the absolute left instead of the child left, as we want the visible area
-        // irrespective of the visible child. Since the view can only scroll horizontally, the
-        // top position is not affected.
-        mTempXY[0] = getPaddingLeft() + boundingLayout.getLeft();
-        mTempXY[1] = child.getTop() + boundingLayout.getTop();
-
-        float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
-        outArea.set(mTempXY[0], mTempXY[1],
-                (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()),
-                (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight()));
+        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, outArea);
     }
 
     @Override
@@ -2094,14 +2097,14 @@
    }
 
    boolean isPointInSelfOverHotseat(int x, int y) {
-       mTempXY[0] = x;
-       mTempXY[1] = y;
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
+       mTempFXY[0] = x;
+       mTempFXY[1] = y;
+       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
        View hotseat = mLauncher.getHotseat();
-       return mTempXY[0] >= hotseat.getLeft() &&
-               mTempXY[0] <= hotseat.getRight() &&
-               mTempXY[1] >= hotseat.getTop() &&
-               mTempXY[1] <= hotseat.getBottom();
+       return mTempFXY[0] >= hotseat.getLeft() &&
+               mTempFXY[0] <= hotseat.getRight() &&
+               mTempFXY[1] >= hotseat.getTop() &&
+               mTempFXY[1] <= hotseat.getBottom();
    }
 
     /**
@@ -2111,13 +2114,8 @@
      */
    private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
        if (mLauncher.isHotseatLayout(layout)) {
-           mTempXY[0] = (int) xy[0];
-           mTempXY[1] = (int) xy[1];
-           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
-           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, mTempXY);
-
-           xy[0] = mTempXY[0];
-           xy[1] = mTempXY[1];
+           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
+           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
        } else {
            mapPointFromSelfToChild(layout, xy);
        }
@@ -2608,13 +2606,14 @@
             DeviceProfile profile = mLauncher.getDeviceProfile();
             Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
         }
-        loc[0] = r.left;
-        loc[1] = r.top;
 
+        mTempFXY[0] = r.left;
+        mTempFXY[1] = r.top;
         setFinalTransitionTransform();
         float cellLayoutScale =
-                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
+                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, mTempFXY, true);
         resetTransitionTransform();
+        Utilities.roundArray(mTempFXY, loc);
 
         if (scale) {
             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 99a8801..8d0259d 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -93,14 +93,16 @@
             Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
-            // Set the hotseat's pivot point to match the workspace's, so that it scales together.
-            DragLayer dragLayer = mLauncher.getDragLayer();
-            int[] workspacePivot = new int[]{(int) mWorkspace.getPivotX(),
-                    (int) mWorkspace.getPivotY()};
-            dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
-            dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
-            hotseat.setPivotX(workspacePivot[0]);
-            hotseat.setPivotY(workspacePivot[1]);
+            if (!hotseat.getRotationMode().isTransposed) {
+                // Set the hotseat's pivot point to match the workspace's, so that it scales together.
+                DragLayer dragLayer = mLauncher.getDragLayer();
+                float[] workspacePivot =
+                        new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
+                dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
+                dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
+                hotseat.setPivotX(workspacePivot[0]);
+                hotseat.setPivotY(workspacePivot[1]);
+            }
             float hotseatScale = hotseatScaleAndTranslation.scale;
             propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator);
 
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index a55ea82..70df97a 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -110,6 +110,10 @@
             "ENABLE_HINTS_IN_OVERVIEW", false,
             "Show chip hints and gleams on the overview screen");
 
+    public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
+            "FAKE_LANDSCAPE_UI", false,
+            "Rotate launcher UI instead of using transposed layout");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 6950a1f..8de2f57 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -17,6 +17,10 @@
 
 package com.android.launcher3.dragndrop;
 
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.View.MeasureSpec.getSize;
+
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -29,12 +33,14 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -42,6 +48,7 @@
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
@@ -52,6 +59,7 @@
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.views.Transposable;
 
 import java.util.ArrayList;
 
@@ -260,10 +268,10 @@
         Rect r = new Rect();
         getViewRectRelativeToSelf(dragView, r);
 
-        int coord[] = new int[2];
+        float coord[] = new float[2];
         float childScale = child.getScaleX();
-        coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
-        coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
+        coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
+        coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
 
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
         // the correct coordinates (above) and use these to determine the final location
@@ -271,8 +279,8 @@
         // We need to account for the scale of the child itself, as the above only accounts for
         // for the scale in parents.
         scale *= childScale;
-        int toX = coord[0];
-        int toY = coord[1];
+        int toX = Math.round(coord[0]);
+        int toY = Math.round(coord[1]);
         float toScale = scale;
         if (child instanceof TextView) {
             TextView tv = (TextView) child;
@@ -551,4 +559,159 @@
     public WorkspaceAndHotseatScrim getScrim() {
         return mScrim;
     }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        RotationMode rotation = mActivity.getRotationMode();
+        int count = getChildCount();
+
+        if (!rotation.isTransposed
+                || getMode(widthMeasureSpec) != EXACTLY
+                || getMode(heightMeasureSpec) != EXACTLY) {
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                child.setRotation(rotation.surfaceRotation);
+            }
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        } else {
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+                if (!(child instanceof Transposable)) {
+                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+                } else {
+                    measureChildWithMargins(child, heightMeasureSpec, 0, widthMeasureSpec, 0);
+
+                    child.setPivotX(child.getMeasuredWidth() / 2);
+                    child.setPivotY(child.getMeasuredHeight() / 2);
+                    child.setRotation(rotation.surfaceRotation);
+                }
+            }
+            setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        RotationMode rotation = mActivity.getRotationMode();
+        if (!rotation.isTransposed) {
+            super.onLayout(changed, left, top, right, bottom);
+            return;
+        }
+
+        final int count = getChildCount();
+
+        final int parentWidth = right - left;
+        final int parentHeight = bottom - top;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+
+            final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
+
+            if (lp instanceof LayoutParams) {
+                final LayoutParams dlp = (LayoutParams) lp;
+                if (dlp.customPosition) {
+                    child.layout(dlp.x, dlp.y, dlp.x + dlp.width, dlp.y + dlp.height);
+                    continue;
+                }
+            }
+
+            final int width = child.getMeasuredWidth();
+            final int height = child.getMeasuredHeight();
+
+            int childLeft;
+            int childTop;
+
+            int gravity = lp.gravity;
+            if (gravity == -1) {
+                gravity = Gravity.TOP | Gravity.START;
+            }
+
+            final int layoutDirection = getLayoutDirection();
+
+            int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+            int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+            int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+            if (child instanceof Transposable) {
+                if (rotation == RotationMode.SEASCAPE) {
+                    if (horizontalGravity == Gravity.RIGHT) {
+                        horizontalGravity = Gravity.LEFT;
+                    } else if (horizontalGravity == Gravity.LEFT) {
+                        horizontalGravity = Gravity.RIGHT;
+                    }
+
+                    if (verticalGravity == Gravity.TOP) {
+                        verticalGravity = Gravity.BOTTOM;
+                    } else if (verticalGravity == Gravity.BOTTOM) {
+                        verticalGravity = Gravity.TOP;
+                    }
+                }
+
+                switch (horizontalGravity) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childTop = (parentHeight - height) / 2 +
+                                lp.topMargin - lp.bottomMargin;
+                        break;
+                    case Gravity.RIGHT:
+                        childTop = width / 2 + lp.rightMargin - height / 2;
+                        break;
+                    case Gravity.LEFT:
+                    default:
+                        childTop = parentHeight - lp.leftMargin - width / 2 - height / 2;
+                }
+
+                switch (verticalGravity) {
+                    case Gravity.CENTER_VERTICAL:
+                        childLeft = (parentWidth - width) / 2 +
+                                lp.leftMargin - lp.rightMargin;
+                        break;
+                    case Gravity.BOTTOM:
+                        childLeft = parentWidth - width / 2 - height / 2 - lp.bottomMargin;
+                        break;
+                    case Gravity.TOP:
+                    default:
+                        childLeft = height / 2 - width / 2 + lp.topMargin;
+                }
+            } else {
+                switch (horizontalGravity) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = (parentWidth - width) / 2 +
+                                lp.leftMargin - lp.rightMargin;
+                        break;
+                    case Gravity.RIGHT:
+                        childLeft = parentWidth - width - lp.rightMargin;
+                        break;
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = lp.leftMargin;
+                }
+
+                switch (verticalGravity) {
+                    case Gravity.TOP:
+                        childTop = lp.topMargin;
+                        break;
+                    case Gravity.CENTER_VERTICAL:
+                        childTop = (parentHeight - height) / 2 +
+                                lp.topMargin - lp.bottomMargin;
+                        break;
+                    case Gravity.BOTTOM:
+                        childTop = parentHeight - height - lp.bottomMargin;
+                        break;
+                    default:
+                        childTop = lp.topMargin;
+                }
+            }
+
+            child.layout(childLeft, childTop, childLeft + width, childTop + height);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4dbff1c..f2eae17 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -869,7 +869,7 @@
         DeviceProfile grid = mLauncher.getDeviceProfile();
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
+        DragLayer parent = mLauncher.getDragLayer();
         int width = getFolderWidth();
         int height = getFolderHeight();
 
@@ -881,8 +881,7 @@
 
         // We need to bound the folder to the currently visible workspace area
         if (mLauncher.getStateManager().getState().overviewUi) {
-            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(),
-                    sTempRect);
+            parent.getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(), sTempRect);
         } else {
             mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
         }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 02242a3..6fa9ba9 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -159,7 +159,7 @@
                     "is dependent on this");
         }
 
-        DeviceProfile grid = launcher.getDeviceProfile();
+        DeviceProfile grid = launcher.getWallpaperDeviceProfile();
         FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
                 .inflate(resId, group, false);
 
@@ -174,7 +174,7 @@
         icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-        icon.mDotRenderer = launcher.getDeviceProfile().mDotRenderer;
+        icon.mDotRenderer = grid.mDotRenderer;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
@@ -508,7 +508,8 @@
     public void drawDot(Canvas canvas) {
         if ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0) {
             Rect iconBounds = mDotParams.iconBounds;
-            BubbleTextView.getIconBounds(this, iconBounds, mLauncher.getDeviceProfile().iconSizePx);
+            BubbleTextView.getIconBounds(this, iconBounds,
+                    mLauncher.getWallpaperDeviceProfile().iconSizePx);
 
             // If we are animating to the accepting state, animate the dot out.
             mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index ac908f4..46df77a 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
 /**
@@ -135,7 +134,7 @@
         mBgColor = ta.getColor(R.styleable.FolderIconPreview_android_colorPrimary, 0);
         ta.recycle();
 
-        DeviceProfile grid = activity.getDeviceProfile();
+        DeviceProfile grid = activity.getWallpaperDeviceProfile();
         previewSize = grid.folderIconSizePx;
 
         basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
diff --git a/src/com/android/launcher3/graphics/RotationMode.java b/src/com/android/launcher3/graphics/RotationMode.java
new file mode 100644
index 0000000..1b2cbdb
--- /dev/null
+++ b/src/com/android/launcher3/graphics/RotationMode.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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.launcher3.graphics;
+
+import android.graphics.Rect;
+
+public abstract class RotationMode {
+
+    public final float surfaceRotation;
+    public final boolean isTransposed;
+
+    private RotationMode(float surfaceRotation) {
+        this.surfaceRotation = surfaceRotation;
+        isTransposed = surfaceRotation != 0;
+    }
+
+    public final void mapRect(Rect rect, Rect out) {
+        mapRect(rect.left, rect.top, rect.right, rect.bottom, out);
+    }
+
+    public void mapRect(int left, int top, int right, int bottom, Rect out) {
+        out.set(left, top, right, bottom);
+    }
+
+    public static RotationMode NORMAL = new RotationMode(0) { };
+
+    public static RotationMode LANDSCAPE = new RotationMode(-90) {
+        @Override
+        public void mapRect(int left, int top, int right, int bottom, Rect out) {
+            out.left = top;
+            out.top = right;
+            out.right = bottom;
+            out.bottom = left;
+        }
+    };
+
+    public static RotationMode SEASCAPE = new RotationMode(90) {
+        @Override
+        public void mapRect(int left, int top, int right, int bottom, Rect out) {
+            out.left = bottom;
+            out.top = left;
+            out.right = top;
+            out.bottom = right;
+        }
+    };
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index c9cdeff..0331a86 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -22,6 +22,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.dot.DotInfo;
 
 /**
@@ -56,6 +57,19 @@
 
     DeviceProfile getDeviceProfile();
 
+    /**
+     * Device profile to be used by UI elements which are shown directly on top of the wallpaper
+     * and whose presentation is tied to the wallpaper (and physical device) and not the activity
+     * configuration.
+     */
+    default DeviceProfile getWallpaperDeviceProfile() {
+        return getDeviceProfile();
+    }
+
+    default RotationMode getRotationMode() {
+        return RotationMode.NORMAL;
+    }
+
     static ActivityContext lookupContext(Context context) {
         if (context instanceof ActivityContext) {
             return (ActivityContext) context;
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 86e1e32..3c81bcf 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -88,7 +88,8 @@
     // Touch is being dispatched through a proxy from InputMonitor
     private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
 
-    protected final int[] mTmpXY = new int[2];
+    protected final float[] mTmpXY = new float[2];
+    protected final float[] mTmpRectPoints = new float[4];
     protected final Rect mHitRect = new Rect();
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -306,14 +307,16 @@
      * @return The factor by which this descendant is scaled relative to this DragLayer.
      */
     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
-        mTmpXY[0] = 0;
-        mTmpXY[1] = 0;
-        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
-
-        r.set(mTmpXY[0], mTmpXY[1],
-                (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
-                (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
-        return scale;
+        mTmpRectPoints[0] = 0;
+        mTmpRectPoints[1] = 0;
+        mTmpRectPoints[2] = descendant.getWidth();
+        mTmpRectPoints[3] = descendant.getHeight();
+        float s = getDescendantCoordRelativeToSelf(descendant, mTmpRectPoints);
+        r.left = Math.round(Math.min(mTmpRectPoints[0], mTmpRectPoints[2]));
+        r.top = Math.round(Math.min(mTmpRectPoints[1], mTmpRectPoints[3]));
+        r.right = Math.round(Math.max(mTmpRectPoints[0], mTmpRectPoints[2]));
+        r.bottom = Math.round(Math.max(mTmpRectPoints[1], mTmpRectPoints[3]));
+        return s;
     }
 
     public float getLocationInDragLayer(View child, int[] loc) {
@@ -323,6 +326,14 @@
     }
 
     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
+        mTmpXY[0] = coord[0];
+        mTmpXY[1] = coord[1];
+        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
+        Utilities.roundArray(mTmpXY, coord);
+        return scale;
+    }
+
+    public float getDescendantCoordRelativeToSelf(View descendant, float[] coord) {
         return getDescendantCoordRelativeToSelf(descendant, coord, false);
     }
 
@@ -338,17 +349,27 @@
      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
      *         assumption fails, we will need to return a pair of scale factors.
      */
-    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
+    public float getDescendantCoordRelativeToSelf(View descendant, float[] coord,
             boolean includeRootScroll) {
         return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
                 coord, includeRootScroll);
     }
 
     /**
+     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
+     */
+    public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
+        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
+    }
+
+    /**
      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
      */
     public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
-        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
+        mTmpXY[0] = coord[0];
+        mTmpXY[1] = coord[1];
+        Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY);
+        Utilities.roundArray(mTmpXY, coord);
     }
 
     public void getViewRectRelativeToSelf(View v, Rect r) {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 77f278a..2574665 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 
@@ -23,6 +26,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -61,12 +65,10 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import static com.android.launcher3.Utilities.mapToRange;
-
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
-
+@TargetApi(Build.VERSION_CODES.Q)
 public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
 
     public static final float SHAPE_PROGRESS_DURATION = 0.15f;
@@ -186,14 +188,16 @@
      * @param v The view to copy
      * @param positionOut Rect that will hold the size and position of v.
      */
-    private void matchPositionOf(Launcher launcher, View v, Rect positionOut) {
-        Utilities.getLocationBoundsForView(launcher, v, positionOut);
-        final LayoutParams lp = new LayoutParams(positionOut.width(), positionOut.height());
+    private void matchPositionOf(Launcher launcher, View v, RectF positionOut) {
+        getLocationBoundsForView(launcher, v, positionOut);
+        final LayoutParams lp = new LayoutParams(
+                Math.round(positionOut.width()),
+                Math.round(positionOut.height()));
         lp.ignoreInsets = true;
 
         // Position the floating view exactly on top of the original
-        lp.leftMargin = positionOut.left;
-        lp.topMargin = positionOut.top;
+        lp.leftMargin = Math.round(positionOut.left);
+        lp.topMargin = Math.round(positionOut.top);
         setLayoutParams(lp);
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
@@ -201,6 +205,57 @@
                 + lp.height);
     }
 
+    /**
+     * Returns the location bounds of a view.
+     * - For DeepShortcutView, we return the bounds of the icon view.
+     * - For BubbleTextView, we return the icon bounds.
+     */
+    private void getLocationBoundsForView(Launcher launcher, View v, RectF outRect) {
+        final boolean isBubbleTextView = v instanceof BubbleTextView;
+        final boolean isFolderIcon = v instanceof FolderIcon;
+
+        // Deep shortcut views have their icon drawn in a separate view.
+        final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
+
+        final View targetView;
+        boolean ignoreTransform = false;
+
+        if (v instanceof DeepShortcutView) {
+            targetView = ((DeepShortcutView) v).getIconView();
+        } else if (fromDeepShortcutView) {
+            DeepShortcutView view = (DeepShortcutView) v.getParent();
+            targetView = view.getIconView();
+        } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
+                && (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP
+                || ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) {
+            targetView = v;
+            ignoreTransform = true;
+        } else {
+            targetView = v;
+        }
+
+        float[] points = new float[] {0, 0, targetView.getWidth(), targetView.getHeight()};
+        Utilities.getDescendantCoordRelativeToAncestor(targetView, launcher.getDragLayer(), points,
+                false, ignoreTransform);
+
+        float viewLocationLeft = Math.min(points[0], points[2]);
+        float viewLocationTop = Math.min(points[1], points[3]);
+
+        final Rect iconRect = new Rect();
+        if (isBubbleTextView && !fromDeepShortcutView) {
+            ((BubbleTextView) v).getIconBounds(iconRect);
+        } else if (isFolderIcon) {
+            ((FolderIcon) v).getPreviewBounds(iconRect);
+        } else {
+            iconRect.set(0, 0, Math.abs(Math.round(points[2] - points[0])),
+                    Math.abs(Math.round(points[3] - points[1])));
+        }
+        viewLocationLeft += iconRect.left;
+        viewLocationTop += iconRect.top;
+        outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + iconRect.width(),
+                viewLocationTop + iconRect.height());
+    }
+
     @WorkerThread
     private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
             Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) {
@@ -411,7 +466,7 @@
      * @param isOpening True if this view replaces the icon for app open animation.
      */
     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
-            boolean hideOriginal, Rect positionOut, boolean isOpening, FloatingIconView recycle) {
+            boolean hideOriginal, RectF positionOut, boolean isOpening, FloatingIconView recycle) {
         if (recycle != null) {
             recycle.recycle();
         }
diff --git a/src/com/android/launcher3/views/Transposable.java b/src/com/android/launcher3/views/Transposable.java
new file mode 100644
index 0000000..929c1aa
--- /dev/null
+++ b/src/com/android/launcher3/views/Transposable.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2019 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.launcher3.views;
+
+import com.android.launcher3.graphics.RotationMode;
+
+/**
+ * Indicates that a view can be transposed.
+ */
+public interface Transposable {
+
+    RotationMode getRotationMode();
+}