Merge "Move floating rotation button handling to Launcher" into sc-v2-dev am: 1aef5f62a1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16101761

Change-Id: Idfcc8a6f075aecb1c54ecae4dbb26f4cb6b83039
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 4e57861..c1c15d1 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -81,7 +81,6 @@
     <dimen name="fab_margin">24dp</dimen>
 
     <dimen name="navigation_key_width">128dp</dimen>
-    <dimen name="navigation_key_padding">25dp</dimen>
 
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">488dp</dimen>
diff --git a/packages/SystemUI/res/values-sw900dp/dimens.xml b/packages/SystemUI/res/values-sw900dp/dimens.xml
index 2cff976..ebae8c4 100644
--- a/packages/SystemUI/res/values-sw900dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw900dp/dimens.xml
@@ -21,11 +21,4 @@
     <dimen name="navigation_side_padding">@dimen/button_size</dimen>
     <dimen name="navigation_key_width">@dimen/button_size</dimen>
     <dimen name="navigation_extra_key_width">@dimen/button_size</dimen>
-
-    <!-- The maximum width of the navigation bar ripples. -->
-    <dimen name="key_button_ripple_max_width">76dp</dimen>
-
-    <!-- The padding around the navigation buttons -->
-    <dimen name="navigation_key_padding">0dp</dimen>
-
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index db6985d..46869a0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -56,11 +56,6 @@
     <!-- The amount by which the arrow is shifted to avoid the finger-->
     <dimen name="navigation_edge_finger_offset">48dp</dimen>
 
-    <dimen name="floating_rotation_button_diameter">40dp</dimen>
-    <dimen name="floating_rotation_button_min_margin">20dp</dimen>
-    <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
-    <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
-
     <!-- Height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
@@ -361,8 +356,6 @@
     <!-- The width/height of the icon of a navigation button -->
     <dimen name="navigation_icon_size">32dp</dimen>
 
-    <dimen name="navigation_key_padding">0dp</dimen>
-
     <!-- The width of the view containing the menu/ime navigation bar icons -->
     <dimen name="navigation_extra_key_width">36dp</dimen>
 
@@ -974,9 +967,6 @@
 
     <dimen name="signal_indicator_to_icon_frame_spacing">3dp</dimen>
 
-    <!-- The maximum width of the navigation bar ripples. -->
-    <dimen name="key_button_ripple_max_width">95dp</dimen>
-
     <!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card
          and the shadow. -->
     <dimen name="fake_shadow_inset">1dp</dimen>
@@ -1166,7 +1156,6 @@
 
     <!-- The absolute side margins of quick settings -->
     <dimen name="quick_settings_bottom_margin_media">8dp</dimen>
-    <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
     <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
     <dimen name="nav_quick_scrub_track_thickness">10dp</dimen>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 4880b12..62e9d8b 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -45,7 +45,9 @@
         ":wm_shell-aidls",
         ":wm_shell_util-sources",
     ],
-
+    resource_dirs: [
+        "res",
+    ],
     static_libs: [
         "PluginCoreLib",
         "androidx.dynamicanimation_dynamicanimation",
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/shared/res/layout/rotate_suggestion.xml
similarity index 90%
rename from packages/SystemUI/res/layout/rotate_suggestion.xml
rename to packages/SystemUI/shared/res/layout/rotate_suggestion.xml
index 1c3eedb..2fb775c 100644
--- a/packages/SystemUI/res/layout/rotate_suggestion.xml
+++ b/packages/SystemUI/shared/res/layout/rotate_suggestion.xml
@@ -18,15 +18,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     >
-
-    <com.android.systemui.navigationbar.buttons.KeyButtonView
+    <com.android.systemui.shared.rotation.FloatingRotationButtonView
         android:id="@+id/rotate_suggestion"
         android:layout_width="@dimen/floating_rotation_button_diameter"
         android:layout_height="@dimen/floating_rotation_button_diameter"
-        android:contentDescription="@string/accessibility_rotate_button"
         android:paddingStart="@dimen/navigation_key_padding"
         android:paddingEnd="@dimen/navigation_key_padding"
         android:layout_gravity="bottom|left"
         android:scaleType="center"
         android:visibility="invisible" />
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/packages/SystemUI/shared/res/values-sw600dp/dimens.xml b/packages/SystemUI/shared/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..5d9e059
--- /dev/null
+++ b/packages/SystemUI/shared/res/values-sw600dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (c) 2021, 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.
+*/
+-->
+<resources>
+    <dimen name="navigation_key_padding">25dp</dimen>
+</resources>
diff --git a/packages/SystemUI/shared/res/values-sw900dp/dimens.xml b/packages/SystemUI/shared/res/values-sw900dp/dimens.xml
new file mode 100644
index 0000000..3efa5e3
--- /dev/null
+++ b/packages/SystemUI/shared/res/values-sw900dp/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (c) 2021, 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.
+*/
+-->
+<resources>
+    <!-- The maximum width of the navigation bar ripples. -->
+    <dimen name="key_button_ripple_max_width">76dp</dimen>
+
+    <!-- The padding around the navigation buttons -->
+    <dimen name="navigation_key_padding">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/shared/res/values/dimens.xml
new file mode 100644
index 0000000..b7f3328
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/dimens.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (c) 2021, 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.
+*/
+-->
+<resources>
+    <!-- The maximum width of the navigation bar ripples. -->
+    <dimen name="key_button_ripple_max_width">95dp</dimen>
+
+    <dimen name="rounded_corner_content_padding">0dp</dimen>
+
+    <dimen name="navigation_key_padding">0dp</dimen>
+
+    <!-- Floating rotation button -->
+    <dimen name="floating_rotation_button_diameter">40dp</dimen>
+    <dimen name="floating_rotation_button_min_margin">20dp</dimen>
+    <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
+    <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
rename to packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 00124ac..53df0f3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -33,12 +33,12 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import android.view.animation.PathInterpolator;
 
 import androidx.annotation.Keep;
 
+import com.android.systemui.shared.R;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 
@@ -49,6 +49,8 @@
     private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
     private static final int ANIMATION_DURATION_SCALE = 350;
     private static final int ANIMATION_DURATION_FADE = 450;
+    private static final Interpolator ALPHA_OUT_INTERPOLATOR =
+            new PathInterpolator(0f, 0f, 0.8f, 1f);
 
     private Paint mRipplePaint;
     private CanvasProperty<Float> mLeftProp;
@@ -336,7 +338,7 @@
 
     private void exitSoftware() {
         ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
-        alphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+        alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
         alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
         alphaAnimator.addListener(mAnimatorListener);
         alphaAnimator.start();
@@ -459,7 +461,7 @@
         final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
                 RenderNodeAnimator.PAINT_ALPHA, 0);
         opacityAnim.setDuration(ANIMATION_DURATION_FADE);
-        opacityAnim.setInterpolator(Interpolators.ALPHA_OUT);
+        opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
         opacityAnim.addListener(mAnimatorListener);
         opacityAnim.addListener(mExitHwTraceAnimator);
         opacityAnim.setTarget(mTargetView);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 4605795..be3d780 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.gestural;
+package com.android.systemui.shared.rotation;
 
+import android.annotation.StringRes;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -27,28 +29,25 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 
-import com.android.systemui.R;
-import com.android.systemui.navigationbar.RotationButton;
-import com.android.systemui.navigationbar.RotationButtonController;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
-import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position;
+import androidx.core.view.OneShotPreDrawListener;
+
+import com.android.systemui.shared.R;
+import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
 
 /**
  * Containing logic for the rotation button on the physical left bottom corner of the screen.
  */
 public class FloatingRotationButton implements RotationButton {
 
-    private static final float BACKGROUND_ALPHA = 0.92f;
     private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
 
     private final WindowManager mWindowManager;
     private final ViewGroup mKeyButtonContainer;
-    private final KeyButtonView mKeyButtonView;
+    private final FloatingRotationButtonView mKeyButtonView;
 
     private final int mContainerSize;
 
-    private KeyButtonDrawable mKeyButtonDrawable;
+    private AnimatedVectorDrawable mAnimatedDrawable;
     private boolean mIsShowing;
     private boolean mCanShow = true;
     private int mDisplayRotation;
@@ -62,12 +61,13 @@
     private RotationButtonUpdatesCallback mUpdatesCallback;
     private Position mPosition;
 
-    public FloatingRotationButton(Context context) {
+    public FloatingRotationButton(Context context, @StringRes int contentDescription) {
         mWindowManager = context.getSystemService(WindowManager.class);
         mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(
                 R.layout.rotate_suggestion, null);
         mKeyButtonView = mKeyButtonContainer.findViewById(R.id.rotate_suggestion);
         mKeyButtonView.setVisibility(View.VISIBLE);
+        mKeyButtonView.setContentDescription(context.getString(contentDescription));
 
         Resources res = context.getResources();
 
@@ -113,6 +113,10 @@
 
         mIsShowing = true;
         int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
+        // TODO(b/200103245): add new window type that has z-index above
+        //  TYPE_NAVIGATION_BAR_PANEL as currently it could be below the taskbar which has
+        //  the same window type
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 mContainerSize,
                 mContainerSize,
@@ -134,14 +138,18 @@
         updateTranslation(mPosition, /* animate */ false);
 
         mWindowManager.addView(mKeyButtonContainer, lp);
-        if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
-            mKeyButtonDrawable.resetAnimation();
-            mKeyButtonDrawable.startAnimation();
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.reset();
+            mAnimatedDrawable.start();
         }
 
-        if (mUpdatesCallback != null) {
-            mUpdatesCallback.onVisibilityChanged(true);
-        }
+        // Notify about visibility only after first traversal so we can properly calculate
+        // the touch region for the button
+        OneShotPreDrawListener.add(mKeyButtonView, () -> {
+            if (mIsShowing && mUpdatesCallback != null) {
+                mUpdatesCallback.onVisibilityChanged(true);
+            }
+        });
 
         return true;
     }
@@ -166,12 +174,10 @@
 
     @Override
     public void updateIcon(int lightIconColor, int darkIconColor) {
-        Color ovalBackgroundColor = Color.valueOf(Color.red(darkIconColor),
-                Color.green(darkIconColor), Color.blue(darkIconColor), BACKGROUND_ALPHA);
-        mKeyButtonDrawable = KeyButtonDrawable.create(mRotationButtonController.getContext(),
-                lightIconColor, darkIconColor, mRotationButtonController.getIconResId(),
-                false /* shadow */, ovalBackgroundColor);
-        mKeyButtonView.setImageDrawable(mKeyButtonDrawable);
+        mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
+                .getDrawable(mRotationButtonController.getIconResId());
+        mKeyButtonView.setImageDrawable(mAnimatedDrawable);
+        mKeyButtonView.setColors(lightIconColor, darkIconColor);
     }
 
     @Override
@@ -185,8 +191,8 @@
     }
 
     @Override
-    public KeyButtonDrawable getImageDrawable() {
-        return mKeyButtonDrawable;
+    public Drawable getImageDrawable() {
+        return mAnimatedDrawable;
     }
 
     @Override
@@ -202,6 +208,7 @@
         }
     }
 
+    @Override
     public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {
         mIsTaskbarVisible = taskbarVisible;
         mIsTaskbarStashed = taskbarStashed;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculator.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
index 3ce51ad..ec3c073 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.navigationbar.gestural
+package com.android.systemui.shared.rotation
 
 import android.view.Gravity
 import android.view.Surface
@@ -7,7 +7,7 @@
  * Calculates gravity and translation that is necessary to display
  * the button in the correct position based on the current state
  */
-internal class FloatingRotationButtonPositionCalculator(
+class FloatingRotationButtonPositionCalculator(
     private val defaultMargin: Int,
     private val taskbarMarginLeft: Int,
     private val taskbarMarginBottom: Int
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
new file mode 100644
index 0000000..e0187f4
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 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.systemui.shared.rotation;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.navigationbar.buttons.KeyButtonRipple;
+
+public class FloatingRotationButtonView extends ImageView {
+
+    private static final float BACKGROUND_ALPHA = 0.92f;
+
+    private final KeyButtonRipple mRipple;
+    private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+
+    public FloatingRotationButtonView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingRotationButtonView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setClickable(true);
+
+        mRipple = new KeyButtonRipple(context, this);
+        setBackground(mRipple);
+        setWillNotDraw(false);
+        forceHasOverlappingRendering(false);
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        if (visibility != View.VISIBLE) {
+            jumpDrawablesToCurrentState();
+        }
+    }
+
+    public void setColors(int lightColor, int darkColor) {
+        getDrawable().setColorFilter(new PorterDuffColorFilter(lightColor, PorterDuff.Mode.SRC_IN));
+
+        final int ovalBackgroundColor = Color.valueOf(Color.red(darkColor),
+                Color.green(darkColor), Color.blue(darkColor), BACKGROUND_ALPHA).toArgb();
+
+        mOvalBgPaint.setColor(ovalBackgroundColor);
+        mRipple.setType(KeyButtonRipple.Type.OVAL);
+    }
+
+    public void setDarkIntensity(float darkIntensity) {
+        mRipple.setDarkIntensity(darkIntensity);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int d = Math.min(getWidth(), getHeight());
+        canvas.drawOval(0, 0, d, d, mOvalBgPaint);
+        super.draw(canvas);
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java
new file mode 100644
index 0000000..89f71eb
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.systemui.shared.rotation;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+/**
+ * Interface of a rotation button that interacts {@link RotationButtonController}.
+ * This interface exists because of the two different styles of rotation button in Sysui,
+ * one in contextual for 3 button nav and a floating rotation button for gestural.
+ */
+public interface RotationButton {
+    default void setRotationButtonController(RotationButtonController rotationButtonController) { }
+    default void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) { }
+
+    default View getCurrentView() {
+        return null;
+    }
+    default boolean show() { return false; }
+    default boolean hide() { return false; }
+    default boolean isVisible() {
+        return false;
+    }
+    default void setCanShowRotationButton(boolean canShow) {}
+    default void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {}
+    default void updateIcon(int lightIconColor, int darkIconColor) { }
+    default void setOnClickListener(View.OnClickListener onClickListener) { }
+    default void setOnHoverListener(View.OnHoverListener onHoverListener) { }
+    default Drawable getImageDrawable() {
+        return null;
+    }
+    default void setDarkIntensity(float darkIntensity) { }
+    default boolean acceptRotationProposal() {
+        return getCurrentView() != null;
+    }
+
+    /**
+     * Callback for updates provided by a rotation button
+     */
+    interface RotationButtonUpdatesCallback {
+        default void onVisibilityChanged(boolean isVisible) {};
+        default void onPositionChanged() {};
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 0f5c03a..2dbd5de 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2021 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.
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar;
+package com.android.systemui.shared.rotation;
+
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
 
@@ -23,48 +25,51 @@
 import android.animation.ObjectAnimator;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.SuppressLint;
 import android.app.StatusBarManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.IRotationWatcher.Stub;
+import android.view.IRotationWatcher;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
 import android.view.WindowInsetsController;
-import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.navigationbar.RotationButton.RotationButtonUpdatesCallback;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.internal.view.RotationPolicy;
+import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.utilities.ViewRippler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.RotationLockController;
 
 import java.util.Optional;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
-/** Contains logic that deals with showing a rotate suggestion button with animation. */
+/**
+ * Contains logic that deals with showing a rotate suggestion button with animation.
+ */
 public class RotationButtonController {
 
     private static final String TAG = "StatusBar/RotationButtonController";
     private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
     private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
+    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
     private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
 
@@ -72,6 +77,7 @@
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
     private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
     private final ViewRippler mViewRippler = new ViewRippler();
+    private final Supplier<Integer> mWindowRotationProvider;
     private RotationButton mRotationButton;
 
     private boolean mIsRecentsAnimationRunning;
@@ -79,17 +85,30 @@
     private int mLastRotationSuggestion;
     private boolean mPendingRotationSuggestion;
     private boolean mHoveringRotationSuggestion;
-    private RotationLockController mRotationLockController;
-    private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
-    private TaskStackListenerImpl mTaskStackListener;
+    private final AccessibilityManager mAccessibilityManager;
+    private final TaskStackListenerImpl mTaskStackListener;
     private Consumer<Integer> mRotWatcherListener;
+
     private boolean mListenersRegistered = false;
     private boolean mIsNavigationBarShowing;
-    private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
+    @SuppressLint("InlinedApi")
+    private @WindowInsetsController.Behavior
+    int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
     private boolean mSkipOverrideUserLockPrefsOnce;
-    private int mLightIconColor;
-    private int mDarkIconColor;
-    private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90;
+    private final int mLightIconColor;
+    private final int mDarkIconColor;
+
+    @DrawableRes
+    private final int mIconCcwStart0ResId;
+    @DrawableRes
+    private final int mIconCcwStart90ResId;
+    @DrawableRes
+    private final int mIconCwStart0ResId;
+    @DrawableRes
+    private final int mIconCwStart90ResId;
+
+    @DrawableRes
+    private int mIconResId;
 
     private final Runnable mRemoveRotationProposal =
             () -> setRotateSuggestionButtonState(false /* visible */);
@@ -97,19 +116,20 @@
             () -> mPendingRotationSuggestion = false;
     private Animator mRotateHideAnimator;
 
-    private final Stub mRotationWatcher = new Stub() {
+
+    private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
         @Override
-        public void onRotationChanged(final int rotation) throws RemoteException {
+        public void onRotationChanged(final int rotation) {
             // We need this to be scheduled as early as possible to beat the redrawing of
             // window in response to the orientation change.
             mMainThreadHandler.postAtFrontOfQueue(() -> {
                 // If the screen rotation changes while locked, potentially update lock to flow with
                 // new screen rotation and hide any showing suggestions.
-                if (mRotationLockController.isRotationLocked()) {
+                if (isRotationLocked()) {
                     if (shouldOverrideUserLockPrefs(rotation)) {
                         setRotationLockedAtAngle(rotation);
                     }
-                    setRotateSuggestionButtonState(false /* visible */, true /* hideImmediately */);
+                    setRotateSuggestionButtonState(false /* visible */, true /* forced */);
                 }
 
                 if (mRotWatcherListener != null) {
@@ -121,27 +141,39 @@
 
     /**
      * Determines if rotation suggestions disabled2 flag exists in flag
+     *
      * @param disable2Flags see if rotation suggestion flag exists in this flag
      * @return whether flag exists
      */
-    static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
+    public static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
         return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
     }
 
-    RotationButtonController(Context context, @ColorInt int lightIconColor,
-            @ColorInt int darkIconColor) {
+    public RotationButtonController(Context context,
+        @ColorInt int lightIconColor, @ColorInt int darkIconColor,
+        @DrawableRes int iconCcwStart0ResId,
+        @DrawableRes int iconCcwStart90ResId,
+        @DrawableRes int iconCwStart0ResId,
+        @DrawableRes int iconCwStart90ResId,
+        Supplier<Integer> windowRotationProvider) {
+
         mContext = context;
         mLightIconColor = lightIconColor;
         mDarkIconColor = darkIconColor;
 
-        mIsNavigationBarShowing = true;
-        mRotationLockController = Dependency.get(RotationLockController.class);
-        mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class);
+        mIconCcwStart0ResId = iconCcwStart0ResId;
+        mIconCcwStart90ResId = iconCcwStart90ResId;
+        mIconCwStart0ResId = iconCwStart0ResId;
+        mIconCwStart90ResId = iconCwStart90ResId;
+        mIconResId = mIconCcwStart90ResId;
+
+        mAccessibilityManager = AccessibilityManager.getInstance(context);
         mTaskStackListener = new TaskStackListenerImpl();
+        mWindowRotationProvider = windowRotationProvider;
     }
 
-    void setRotationButton(RotationButton rotationButton,
-                           RotationButtonUpdatesCallback updatesCallback) {
+    public void setRotationButton(RotationButton rotationButton,
+                                  RotationButtonUpdatesCallback updatesCallback) {
         mRotationButton = rotationButton;
         mRotationButton.setRotationButtonController(this);
         mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
@@ -149,7 +181,24 @@
         mRotationButton.setUpdatesCallback(updatesCallback);
     }
 
-    void registerListeners() {
+    public Context getContext() {
+        return mContext;
+    }
+
+    public void init() {
+        registerListeners();
+        if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) {
+            // Currently there is no accelerometer sensor on non-default display, disable fixed
+            // rotation for non-default display
+            onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
+        }
+    }
+
+    public void onDestroy() {
+        unregisterListeners();
+    }
+
+    public void registerListeners() {
         if (mListenersRegistered) {
             return;
         }
@@ -157,18 +206,19 @@
         mListenersRegistered = true;
         try {
             WindowManagerGlobal.getWindowManagerService()
-                    .watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId());
+                    .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
         } catch (IllegalArgumentException e) {
             mListenersRegistered = false;
             Log.w(TAG, "RegisterListeners for the display failed");
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+            return;
         }
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
     }
 
-    void unregisterListeners() {
+    public void unregisterListeners() {
         if (!mListenersRegistered) {
             return;
         }
@@ -177,34 +227,31 @@
         try {
             WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+            return;
         }
 
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
 
-    void setRotationCallback(Consumer<Integer> watcher) {
+    public void setRotationCallback(Consumer<Integer> watcher) {
         mRotWatcherListener = watcher;
     }
 
-    void setRotationLockedAtAngle(int rotationSuggestion) {
-        mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
+    public void setRotationLockedAtAngle(int rotationSuggestion) {
+        RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
     }
 
     public boolean isRotationLocked() {
-        return mRotationLockController.isRotationLocked();
+        return RotationPolicy.isRotationLocked(mContext);
     }
 
-    void setRotateSuggestionButtonState(boolean visible) {
-        setRotateSuggestionButtonState(visible, false /* hideImmediately */);
+    public void setRotateSuggestionButtonState(boolean visible) {
+        setRotateSuggestionButtonState(visible, false /* force */);
     }
 
-    /**
-     * Change the visibility of rotate suggestion button. If {@code hideImmediately} is true,
-     * it doesn't wait until the completion of the running animation.
-     */
-    void setRotateSuggestionButtonState(final boolean visible, final boolean hideImmediately) {
-        // At any point the the button can become invisible because an a11y service became active.
+    void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+        // At any point the button can become invisible because an a11y service became active.
         // Similarly, a call to make the button visible may be rejected because an a11y service is
         // active. Must account for this.
         // Rerun a show animation to indicate change but don't rerun a hide animation
@@ -213,7 +260,7 @@
         final View view = mRotationButton.getCurrentView();
         if (view == null) return;
 
-        final KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable();
+        final Drawable currentDrawable = mRotationButton.getImageDrawable();
         if (currentDrawable == null) return;
 
         // Clear any pending suggestion flag as it has either been nullified or is being shown
@@ -232,11 +279,13 @@
             view.setAlpha(1f);
 
             // Run the rotate icon's animation if it has one
-            if (currentDrawable.canAnimate()) {
-                currentDrawable.resetAnimation();
-                currentDrawable.startAnimation();
+            if (currentDrawable instanceof AnimatedVectorDrawable) {
+                ((AnimatedVectorDrawable) currentDrawable).reset();
+                ((AnimatedVectorDrawable) currentDrawable).start();
             }
 
+            // TODO(b/187754252): No idea why this doesn't work. If we remove the "false"
+            //  we see the animation show the pressed state... but it only shows the first time.
             if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
 
             // Set visibility unless a11y service is active.
@@ -244,7 +293,7 @@
         } else { // Hide
             mViewRippler.stop(); // Prevent any pending ripples, force hide or not
 
-            if (hideImmediately) {
+            if (force) {
                 // If a hide animator is running stop it and make invisible
                 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
                     mRotateHideAnimator.pause();
@@ -258,7 +307,7 @@
 
             ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
             fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
-            fadeOut.setInterpolator(Interpolators.LINEAR);
+            fadeOut.setInterpolator(LINEAR_INTERPOLATOR);
             fadeOut.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -271,29 +320,34 @@
         }
     }
 
-    void setRecentsAnimationRunning(boolean running) {
+    public void setDarkIntensity(float darkIntensity) {
+        mRotationButton.setDarkIntensity(darkIntensity);
+    }
+
+    public void setRecentsAnimationRunning(boolean running) {
         mIsRecentsAnimationRunning = running;
         updateRotationButtonStateInOverview();
     }
 
-    void setHomeRotationEnabled(boolean enabled) {
+    public void setHomeRotationEnabled(boolean enabled) {
         mHomeRotationEnabled = enabled;
         updateRotationButtonStateInOverview();
     }
 
     private void updateRotationButtonStateInOverview() {
         if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
-            setRotateSuggestionButtonState(false, true /* hideImmediately */ );
+            setRotateSuggestionButtonState(false, true /* hideImmediately */);
         }
     }
 
-    void setDarkIntensity(float darkIntensity) {
-        mRotationButton.setDarkIntensity(darkIntensity);
-    }
+    public void onRotationProposal(int rotation, boolean isValid) {
+        int windowRotation = mWindowRotationProvider.get();
 
-    void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
-        if (!mRotationButton.acceptRotationProposal() || (!mHomeRotationEnabled
-                && mIsRecentsAnimationRunning)) {
+        if (!mRotationButton.acceptRotationProposal()) {
+            return;
+        }
+
+        if (!mHomeRotationEnabled && mIsRecentsAnimationRunning) {
             return;
         }
 
@@ -316,13 +370,9 @@
         mLastRotationSuggestion = rotation; // Remember rotation for click
         final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
         if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
-            mIconResId = rotationCCW
-                    ? R.drawable.ic_sysbar_rotate_button_ccw_start_90
-                    : R.drawable.ic_sysbar_rotate_button_cw_start_90;
+            mIconResId = rotationCCW ? mIconCcwStart0ResId : mIconCwStart0ResId;
         } else { // 90 or 270
-            mIconResId = rotationCCW
-                    ? R.drawable.ic_sysbar_rotate_button_ccw_start_0
-                    : R.drawable.ic_sysbar_rotate_button_ccw_start_0;
+            mIconResId = rotationCCW ? mIconCcwStart90ResId : mIconCwStart90ResId;
         }
         mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
 
@@ -339,23 +389,31 @@
         }
     }
 
-    void onDisable2FlagChanged(int state2) {
+    public void onDisable2FlagChanged(int state2) {
         final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
         if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
     }
 
-    void onNavigationBarWindowVisibilityChange(boolean showing) {
+    public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
+        if (DEFAULT_DISPLAY != displayId) {
+            return;
+        }
+
+        if (mBehavior != behavior) {
+            mBehavior = behavior;
+            showPendingRotationButtonIfNeeded();
+        }
+    }
+
+    public void onNavigationBarWindowVisibilityChange(boolean showing) {
         if (mIsNavigationBarShowing != showing) {
             mIsNavigationBarShowing = showing;
             showPendingRotationButtonIfNeeded();
         }
     }
 
-    void onBehaviorChanged(@Behavior int behavior) {
-        if (mBehavior != behavior) {
-            mBehavior = behavior;
-            showPendingRotationButtonIfNeeded();
-        }
+    public void onTaskbarStateChange(boolean visible, boolean stashed) {
+        getRotationButton().onTaskbarStateChanged(visible, stashed);
     }
 
     private void showPendingRotationButtonIfNeeded() {
@@ -364,31 +422,33 @@
         }
     }
 
-    /** Return true when either the nav bar is visible or it's in visual immersive mode. */
+    /**
+     * Return true when either the task bar is visible or it's in visual immersive mode.
+     */
+    @SuppressLint("InlinedApi")
     private boolean canShowRotationButton() {
         return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
     }
 
-    public Context getContext() {
-        return mContext;
-    }
-
-    RotationButton getRotationButton() {
-        return mRotationButton;
-    }
-
-    public @DrawableRes int getIconResId() {
+    @DrawableRes
+    public int getIconResId() {
         return mIconResId;
     }
 
-    public @ColorInt int getLightIconColor() {
+    @ColorInt
+    public int getLightIconColor() {
         return mLightIconColor;
     }
 
-    public @ColorInt int getDarkIconColor() {
+    @ColorInt
+    public int getDarkIconColor() {
         return mDarkIconColor;
     }
 
+    public RotationButton getRotationButton() {
+        return mRotationButton;
+    }
+
     private void onRotateSuggestionClick(View v) {
         mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
         incrementNumAcceptedRotationSuggestionsIfNeeded();
@@ -420,7 +480,7 @@
      * avoid losing original user rotation when display rotation is changed by entering the fixed
      * orientation overview.
      */
-    void setSkipOverrideUserLockPrefsOnce() {
+    public void setSkipOverrideUserLockPrefsOnce() {
         mSkipOverrideUserLockPrefsOnce = true;
     }
 
@@ -451,7 +511,7 @@
     }
 
     private int computeRotationProposalTimeout() {
-        return mAccessibilityManagerWrapper.getRecommendedTimeoutMillis(
+        return mAccessibilityManager.getRecommendedTimeoutMillis(
                 mHoveringRotationSuggestion ? 16000 : 5000,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
@@ -513,11 +573,15 @@
         ROTATION_SUGGESTION_ACCEPTED(207);
 
         private final int mId;
+
         RotationButtonEvent(int id) {
             mId = id;
         }
-        @Override public int getId() {
+
+        @Override
+        public int getId() {
             return mId;
         }
     }
 }
+
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 7809b5f..6a1eae7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -131,6 +131,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -944,7 +946,6 @@
         // not valid.  Just ignore the rotation in this case.
         if (!mNavigationBarView.isAttachedToWindow()) return;
 
-        final int winRotation = mNavigationBarView.getDisplay().getRotation();
         final boolean rotateSuggestionsDisabled = RotationButtonController
                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
         final RotationButtonController rotationButtonController =
@@ -953,7 +954,6 @@
 
         if (RotationContextButton.DEBUG_ROTATION) {
             Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
-                    + ", winRotation=" + Surface.rotationToString(winRotation)
                     + ", isValid=" + isValid + ", mNavBarWindowState="
                     + StatusBarManager.windowStateToString(mNavigationBarWindowState)
                     + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
@@ -963,7 +963,7 @@
         // Respect the disabled flag, no need for action as flag change callback will handle hiding
         if (rotateSuggestionsDisabled) return;
 
-        rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
+        rotationButtonController.onRotationProposal(rotation, isValid);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c02cc8d..cba76e0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -67,7 +67,6 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.ContextualButton;
 import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
@@ -76,9 +75,11 @@
 import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
 import com.android.systemui.navigationbar.buttons.RotationContextButton;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.navigationbar.gestural.FloatingRotationButton;
+import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
+import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -322,9 +323,15 @@
         mContextualButtonGroup.addButton(accessibilityButton);
         mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
                 mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
-        mFloatingRotationButton = new FloatingRotationButton(context);
-        mRotationButtonController = new RotationButtonController(mLightContext,
-                mLightIconColor, mDarkIconColor);
+        mFloatingRotationButton = new FloatingRotationButton(context,
+                R.string.accessibility_rotate_button);
+        mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
+                mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
+                R.drawable.ic_sysbar_rotate_button_ccw_start_90,
+                R.drawable.ic_sysbar_rotate_button_cw_start_0,
+                R.drawable.ic_sysbar_rotate_button_cw_start_90,
+                () -> getDisplay().getRotation());
+
         updateRotationButton();
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
@@ -661,7 +668,7 @@
     }
 
     public void setBehavior(@Behavior int behavior) {
-        mRotationButtonController.onBehaviorChanged(behavior);
+        mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, behavior);
     }
 
     @Override
@@ -1277,6 +1284,7 @@
             mButtonDispatchers.valueAt(i).onDestroy();
         }
         if (mRotationButtonController != null) {
+            mFloatingRotationButton.hide();
             mRotationButtonController.unregisterListeners();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
deleted file mode 100644
index 3486c6e..0000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.navigationbar;
-
-import android.view.View;
-
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-
-/** Interface of a rotation button that interacts {@link RotationButtonController}. */
-public interface RotationButton {
-    void setRotationButtonController(RotationButtonController rotationButtonController);
-    void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback);
-    View getCurrentView();
-    boolean show();
-    boolean hide();
-    boolean isVisible();
-    void updateIcon(int lightIconColor, int darkIconColor);
-    void setOnClickListener(View.OnClickListener onClickListener);
-    void setOnHoverListener(View.OnHoverListener onHoverListener);
-    KeyButtonDrawable getImageDrawable();
-    void setDarkIntensity(float darkIntensity);
-    default void setCanShowRotationButton(boolean canShow) {}
-    default boolean acceptRotationProposal() {
-        return getCurrentView() != null;
-    }
-
-    /**
-     * Callback for updates provided by a rotation button
-     */
-    interface RotationButtonUpdatesCallback {
-        void onVisibilityChanged(boolean isVisible);
-        void onPositionChanged();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
index ebb67af..ac014b5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
@@ -21,8 +21,8 @@
 import android.content.Context;
 import android.view.View;
 
-import com.android.systemui.navigationbar.RotationButton;
-import com.android.systemui.navigationbar.RotationButtonController;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
 
 /** Containing logic for the rotation button in nav bar. */
 public class RotationContextButton extends ContextualButton implements RotationButton {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index a6ff2e8..85bc634 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.view.Display;
 import android.view.View;
 import android.view.WindowInsetsController;
 
@@ -31,6 +32,8 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
 import org.junit.Before;
@@ -39,6 +42,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Supplier;
+
 /** atest NavigationBarRotationContextTest */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -50,6 +55,8 @@
             InstrumentationRegistry.getContext(), getLeakCheck());
     private RotationButtonController mRotationButtonController;
     private RotationButton mRotationButton;
+    private int mWindowRotation = DEFAULT_ROTATE;
+    private Supplier<Integer> mWindowRotationSupplier = () -> mWindowRotation;
 
     @Before
     public void setup() {
@@ -58,7 +65,15 @@
 
         final View view = new View(mContext);
         mRotationButton = mock(RotationButton.class);
-        mRotationButtonController = new RotationButtonController(mContext, 0, 0);
+        mRotationButtonController = new RotationButtonController(mContext,
+                /* lightIconColor */ 0,
+                /* darkIconColor */ 0,
+                /* iconCcwStart0 */ 0,
+                /* iconCcwStart90 */ 0,
+                /* iconCwStart0 */ 0,
+                /* iconCwStart90 */ 0,
+                mWindowRotationSupplier
+        );
         mRotationButtonController.setRotationButton(mRotationButton,
                 new RotationButton.RotationButtonUpdatesCallback() {
                     @Override
@@ -77,16 +92,16 @@
 
     @Test
     public void testOnInvalidRotationProposal() {
-        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
-                false /* isValid */);
+        mWindowRotation = DEFAULT_ROTATE + 1;
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, false /* isValid */);
         verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
                 false /* visible */);
     }
 
     @Test
     public void testOnSameRotationProposal() {
-        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE,
-                true /* isValid */);
+        mWindowRotation = DEFAULT_ROTATE;
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
         verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
                 false /* visible */);
     }
@@ -94,17 +109,17 @@
     @Test
     public void testOnRotationProposalShowButtonShowNav() {
         // No navigation bar should not call to set visibility state
-        mRotationButtonController.onBehaviorChanged(
+        mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY,
                 WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
         mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
                 false /* visible */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
                 true /* visible */);
+        mWindowRotation = DEFAULT_ROTATE + 1;
 
         // No navigation bar with rotation change should not call to set visibility state
-        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
-                true /* isValid */);
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
                 false /* visible */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
@@ -124,10 +139,10 @@
                 false /* visible */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
                 true /* visible */);
+        mWindowRotation = DEFAULT_ROTATE + 1;
 
         // Navigation bar is visible and rotation requested
-        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
-                true /* isValid */);
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
         verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
                 true /* visible */);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
index 0a20001..36e02cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
@@ -4,7 +4,8 @@
 import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.navigationbar.gestural.FloatingRotationButtonPositionCalculator.Position
+import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator
+import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith