diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index fb2ee1c5a7..f237d26aa6 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -30,6 +30,9 @@
#EBffffff
#99000000
+ #FFF
+ #99000000
+
#FFFFFFFF
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 88c98c0f59..151b8e4604 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -208,6 +208,9 @@
Skip
+
+ Rotate screen
+
Taskbar education appeared
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 648a16e305..bc6348d8d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -345,7 +345,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
*/
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
- return mControllers.taskbarViewController.isEventOverAnyItem(ev);
+ return mControllers.taskbarViewController.isEventOverAnyItem(ev)
+ || mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
}
public boolean isDraggingItem() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 113bd91f00..11349bba87 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y_LONG_CLICK;
@@ -38,6 +40,7 @@ import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.graphics.Region;
@@ -45,6 +48,7 @@ import android.graphics.Region.Op;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.util.Property;
import android.view.Gravity;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnHoverListener;
@@ -57,11 +61,12 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
-import com.android.launcher3.taskbar.contextual.RotationButton;
-import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.Themes;
import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.rotation.FloatingRotationButton;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
import java.util.ArrayList;
import java.util.function.IntPredicate;
@@ -103,12 +108,16 @@ public class NavbarButtonsViewController {
this::updateNavButtonTranslationY);
private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
this::updateNavButtonTranslationY);
+ private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
+
+ private final Rect mFloatingRotationButtonBounds = new Rect();
// Initialized in init.
private TaskbarControllers mControllers;
private View mA11yButton;
private int mSysuiStateFlags;
private View mBackButton;
+ private FloatingRotationButton mFloatingRotationButton;
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
mContext = context;
@@ -198,9 +207,13 @@ public class NavbarButtonsViewController {
addButton(mEndContextualContainer, R.id.rotate_suggestion,
R.layout.taskbar_contextual_button));
rotationButton.hide();
- mControllers.rotationButtonController.setRotationButton(rotationButton);
+ mControllers.rotationButtonController.setRotationButton(rotationButton, null);
} else {
- mControllers.rotationButtonController.setRotationButton(new RotationButton() {});
+ mFloatingRotationButton = new FloatingRotationButton(mContext,
+ R.string.accessibility_rotate_button);
+ mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
+ mRotationButtonListener);
+
View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
mStartContextualContainer, mControllers.navButtonController, R.id.back);
imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
@@ -405,8 +418,28 @@ public class NavbarButtonsViewController {
return buttonView;
}
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
+ }
+
public void onDestroy() {
mPropertyHolders.clear();
+ mControllers.rotationButtonController.unregisterListeners();
+ if (mFloatingRotationButton != null) {
+ mFloatingRotationButton.hide();
+ }
+ }
+
+ private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
+ @Override
+ public void onVisibilityChanged(boolean isVisible) {
+ if (isVisible) {
+ mFloatingRotationButton.getCurrentView()
+ .getBoundsOnScreen(mFloatingRotationButtonBounds);
+ } else {
+ mFloatingRotationButtonBounds.setEmpty();
+ }
+ }
}
private class RotationButtonImpl implements RotationButton {
@@ -424,6 +457,8 @@ public class NavbarButtonsViewController {
mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
.getDrawable(rotationButtonController.getIconResId());
mButton.setImageDrawable(mImageDrawable);
+ mButton.setContentDescription(mButton.getResources()
+ .getString(R.string.accessibility_rotate_button));
mImageDrawable.setCallback(mButton);
}
@@ -433,17 +468,19 @@ public class NavbarButtonsViewController {
}
@Override
- public void show() {
+ public boolean show() {
mButton.setVisibility(View.VISIBLE);
mState |= FLAG_ROTATION_BUTTON_VISIBLE;
applyState();
+ return true;
}
@Override
- public void hide() {
+ public boolean hide() {
mButton.setVisibility(View.GONE);
mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
applyState();
+ return true;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index db3156bfb9..72d9d5b0b1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -60,7 +60,6 @@ import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SettingsCache;
@@ -71,6 +70,7 @@ import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
@@ -147,8 +147,14 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
new TaskbarDragController(this),
buttonController,
new NavbarButtonsViewController(this, navButtonsView),
- new RotationButtonController(this, R.color.popup_color_primary_light,
- R.color.popup_color_primary_light),
+ new RotationButtonController(this,
+ c.getColor(R.color.rotation_button_light_color),
+ c.getColor(R.color.rotation_button_dark_color),
+ 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()),
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
new TaskbarScrimViewController(this, taskbarScrimView),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index d739eeac26..08a79c064d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -17,7 +17,7 @@ package com.android.launcher3.taskbar;
import androidx.annotation.NonNull;
-import com.android.launcher3.taskbar.contextual.RotationButtonController;
+import com.android.systemui.shared.rotation.RotationButtonController;
/**
* Hosts various taskbar controllers to facilitate passing between one another.
@@ -80,9 +80,7 @@ public class TaskbarControllers {
public void init(TaskbarSharedState sharedState) {
taskbarDragController.init(this);
navbarButtonsViewController.init(this);
- if (taskbarActivityContext.isThreeButtonNav()) {
- rotationButtonController.init();
- }
+ rotationButtonController.init();
taskbarDragLayerController.init(this);
taskbarViewController.init(this);
taskbarScrimViewController.init(this);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 5986e22fab..089c265209 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -16,8 +16,7 @@
package com.android.launcher3.taskbar;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
@@ -91,7 +90,7 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service);
Display display =
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
- mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null);
+ mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
mNavButtonController = new TaskbarNavButtonController(service);
mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
mComponentCallbacks = new ComponentCallbacks() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index d11eb36feb..acb4aa8632 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -94,6 +94,7 @@ public class TaskbarStashController {
private final SharedPreferences mPrefs;
private final int mStashedHeight;
private final int mUnstashedHeight;
+ private final SystemUiProxy mSystemUiProxy;
// Initialized in init.
private TaskbarControllers mControllers;
@@ -127,6 +128,7 @@ public class TaskbarStashController {
mPrefs = Utilities.getPrefs(mActivity);
final Resources resources = mActivity.getResources();
mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
}
@@ -155,8 +157,7 @@ public class TaskbarStashController {
!mActivity.isUserSetupComplete() || sharedState.setupUIVisible);
applyState();
- SystemUiProxy.INSTANCE.get(mActivity)
- .notifyTaskbarStatus(/* visible */ false, /* stashed */ isStashedInApp());
+ notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
}
/**
@@ -440,8 +441,7 @@ public class TaskbarStashController {
mControllers.uiController.onStashedInAppChanged();
}
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAG_IN_APP)) {
- SystemUiProxy.INSTANCE.get(mActivity)
- .notifyTaskbarStatus(/* visible */ hasAnyFlag(FLAG_IN_APP),
+ notifyStashChange(/* visible */ hasAnyFlag(FLAG_IN_APP),
/* stashed */ isStashedInApp());
}
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
@@ -453,6 +453,11 @@ public class TaskbarStashController {
}
}
+ private void notifyStashChange(boolean visible, boolean stashed) {
+ mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
+ mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
+ }
+
private class StatePropertyHolder {
private final IntPredicate mStashCondition;
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
deleted file mode 100644
index 40930972d1..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- * 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.taskbar.contextual;
-
-import android.graphics.drawable.AnimatedVectorDrawable;
-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.
- * Keeping the interface for eventual migration of floating button, so some methods are
- * pass through to "super" while others are trivially implemented.
- *
- * Changes:
- * * Directly use AnimatedVectorDrawable instead of KeyButtonDrawable
- */
-public interface RotationButton {
- default void setRotationButtonController(RotationButtonController rotationButtonController) { }
-
- default View getCurrentView() {
- return null;
- }
- default void show() { }
- default void hide() { }
- default boolean isVisible() {
- return false;
- }
-
- default void updateIcon(int lightIconColor, int darkIconColor) { }
- default void setOnClickListener(View.OnClickListener onClickListener) { }
- default void setOnHoverListener(View.OnHoverListener onHoverListener) { }
- default AnimatedVectorDrawable getImageDrawable() {
- return null;
- }
- default void setDarkIntensity(float darkIntensity) { }
- default boolean acceptRotationProposal() {
- return getCurrentView() != null;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
deleted file mode 100644
index c776f16d2a..0000000000
--- a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * 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.
- * 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.taskbar.contextual;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-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.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.IRotationWatcher;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowInsetsController;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.internal.view.RotationPolicy;
-import com.android.launcher3.R;
-import com.android.launcher3.util.DisplayController;
-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 java.util.Optional;
-
-/**
- * Copied over from the SysUI equivalent class. Known issues/things not ported over
- * * When rotation button visible and in auto-hide mode, we ask auto-hide controller to
- * keep the navbar around longer. Will need to implement if we use auto-hide on taskbar
- *
- * 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 int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
-
- private final Context mContext;
- private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
- private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
- private final ViewRippler mViewRippler = new ViewRippler();
- private final DisplayController mDisplayController;
- private RotationButton mRotationButton;
-
- private int mLastRotationSuggestion;
- private boolean mPendingRotationSuggestion;
- private boolean mHoveringRotationSuggestion;
- private final AccessibilityManager mAccessibilityManager;
- private final TaskStackListenerImpl mTaskStackListener;
- private boolean mListenersRegistered = false;
- private boolean mIsTaskbarShowing;
- @SuppressLint("InlinedApi")
- private @WindowInsetsController.Behavior
- int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
- private boolean mSkipOverrideUserLockPrefsOnce;
- private final int mLightIconColor;
- private final int mDarkIconColor;
- private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90;
-
- private final Runnable mRemoveRotationProposal =
- () -> setRotateSuggestionButtonState(false /* visible */);
- private final Runnable mCancelPendingRotationProposal =
- () -> mPendingRotationSuggestion = false;
- private Animator mRotateHideAnimator;
-
-
- private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
- @Override
- 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 (isRotationLocked()) {
- if (shouldOverrideUserLockPrefs(rotation)) {
- setRotationLockedAtAngle(rotation);
- }
- setRotateSuggestionButtonState(false /* visible */, true /* forced */);
- }
- });
- }
- };
-
- /**
- * 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) {
- return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
- }
-
- public RotationButtonController(Context context, @ColorInt int lightIconColor,
- @ColorInt int darkIconColor) {
- mContext = context;
- mLightIconColor = lightIconColor;
- mDarkIconColor = darkIconColor;
-
- mAccessibilityManager = AccessibilityManager.getInstance(context);
- mTaskStackListener = new TaskStackListenerImpl();
- mDisplayController = DisplayController.INSTANCE.get(context);
- }
-
- public void setRotationButton(RotationButton rotationButton) {
- mRotationButton = rotationButton;
- mRotationButton.setRotationButtonController(this);
- mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
- mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
- }
-
- 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();
- }
-
- private void registerListeners() {
- if (mListenersRegistered) {
- return;
- }
-
- mListenersRegistered = true;
- try {
- WindowManagerGlobal.getWindowManagerService()
- .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
- } catch (IllegalArgumentException e) {
- mListenersRegistered = false;
- Log.w(TAG, "RegisterListeners for the display failed");
- } catch (RemoteException e) {
- Log.e(TAG, "RegisterListeners caught a RemoteException", e);
- return;
- }
-
- TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
- }
-
- void unregisterListeners() {
- if (!mListenersRegistered) {
- return;
- }
-
- mListenersRegistered = false;
- try {
- WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
- } catch (RemoteException e) {
- Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
- return;
- }
-
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
- }
-
- void setRotationLockedAtAngle(int rotationSuggestion) {
- RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
- }
-
- public boolean isRotationLocked() {
- return RotationPolicy.isRotationLocked(mContext);
- }
-
- public void setRotateSuggestionButtonState(boolean visible) {
- setRotateSuggestionButtonState(visible, false /* force */);
- }
-
- 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
- if (!visible && !mRotationButton.isVisible()) return;
-
- final View view = mRotationButton.getCurrentView();
- if (view == null) return;
-
- final AnimatedVectorDrawable currentDrawable = mRotationButton.getImageDrawable();
- if (currentDrawable == null) return;
-
- // Clear any pending suggestion flag as it has either been nullified or is being shown
- mPendingRotationSuggestion = false;
- mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
-
- // Handle the visibility change and animation
- if (visible) { // Appear and change (cannot force)
- // Stop and clear any currently running hide animations
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- mRotateHideAnimator.cancel();
- }
- mRotateHideAnimator = null;
-
- // Reset the alpha if any has changed due to hide animation
- view.setAlpha(1f);
-
- // Run the rotate icon's animation if it has one
- currentDrawable.reset();
- 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.
- mRotationButton.show();
- } else { // Hide
- mViewRippler.stop(); // Prevent any pending ripples, force hide or not
-
- if (force) {
- // If a hide animator is running stop it and make invisible
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- mRotateHideAnimator.pause();
- }
- mRotationButton.hide();
- return;
- }
-
- // Don't start any new hide animations if one is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
-
- ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
- fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
- fadeOut.setInterpolator(LINEAR);
- fadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRotationButton.hide();
- }
- });
-
- mRotateHideAnimator = fadeOut;
- fadeOut.start();
- }
- }
-
- void setDarkIntensity(float darkIntensity) {
- mRotationButton.setDarkIntensity(darkIntensity);
- }
-
- public void onRotationProposal(int rotation, boolean isValid) {
- int windowRotation = mDisplayController.getInfo().rotation;
-
- if (!mRotationButton.acceptRotationProposal()) {
- return;
- }
-
- // This method will be called on rotation suggestion changes even if the proposed rotation
- // is not valid for the top app. Use invalid rotation choices as a signal to remove the
- // rotate button if shown.
- if (!isValid) {
- setRotateSuggestionButtonState(false /* visible */);
- return;
- }
-
- // If window rotation matches suggested rotation, remove any current suggestions
- if (rotation == windowRotation) {
- mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
- setRotateSuggestionButtonState(false /* visible */);
- return;
- }
-
- // Prepare to show the navbar icon by updating the icon style to change anim params
- 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;
- } else { // 90 or 270
- mIconResId = rotationCCW
- ? R.drawable.ic_sysbar_rotate_button_ccw_start_0
- : R.drawable.ic_sysbar_rotate_button_ccw_start_0;
- }
- mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
-
- if (canShowRotationButton()) {
- // The navbar is visible / it's in visual immersive mode, so show the icon right away
- showAndLogRotationSuggestion();
- } else {
- // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
- // visible given some time limit.
- mPendingRotationSuggestion = true;
- mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
- mMainThreadHandler.postDelayed(mCancelPendingRotationProposal,
- NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
- }
- }
-
- public void onDisable2FlagChanged(int state2) {
- final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
- if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
- }
-
- public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
- if (DEFAULT_DISPLAY != displayId) {
- return;
- }
-
- if (mBehavior != behavior) {
- mBehavior = behavior;
- showPendingRotationButtonIfNeeded();
- }
- }
-
- public void onTaskBarVisibilityChange(boolean showing) {
- if (mIsTaskbarShowing != showing) {
- mIsTaskbarShowing = showing;
- showPendingRotationButtonIfNeeded();
- }
- }
-
- private void showPendingRotationButtonIfNeeded() {
- if (canShowRotationButton() && mPendingRotationSuggestion) {
- showAndLogRotationSuggestion();
- }
- }
-
- /** Return true when either the task bar is visible or it's in visual immersive mode. */
- @SuppressLint("InlinedApi")
- private boolean canShowRotationButton() {
- return mIsTaskbarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
- }
-
- public @DrawableRes
- int getIconResId() {
- return mIconResId;
- }
-
- public @ColorInt int getLightIconColor() {
- return mLightIconColor;
- }
-
- public @ColorInt int getDarkIconColor() {
- return mDarkIconColor;
- }
-
- private void onRotateSuggestionClick(View v) {
- mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
- incrementNumAcceptedRotationSuggestionsIfNeeded();
- setRotationLockedAtAngle(mLastRotationSuggestion);
- }
-
- private boolean onRotateSuggestionHover(View v, MotionEvent event) {
- final int action = event.getActionMasked();
- mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
- || (action == MotionEvent.ACTION_HOVER_MOVE);
- rescheduleRotationTimeout(true /* reasonHover */);
- return false; // Must return false so a11y hover events are dispatched correctly.
- }
-
- private void onRotationSuggestionsDisabled() {
- // Immediately hide the rotate button and clear any planned removal
- setRotateSuggestionButtonState(false /* visible */, true /* force */);
- mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
- }
-
- private void showAndLogRotationSuggestion() {
- setRotateSuggestionButtonState(true /* visible */);
- rescheduleRotationTimeout(false /* reasonHover */);
- mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN);
- }
-
- /**
- * Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to
- * avoid losing original user rotation when display rotation is changed by entering the fixed
- * orientation overview.
- */
- void setSkipOverrideUserLockPrefsOnce() {
- mSkipOverrideUserLockPrefsOnce = true;
- }
-
- private boolean shouldOverrideUserLockPrefs(final int rotation) {
- if (mSkipOverrideUserLockPrefsOnce) {
- mSkipOverrideUserLockPrefsOnce = false;
- return false;
- }
- // Only override user prefs when returning to the natural rotation (normally portrait).
- // Don't let apps that force landscape or 180 alter user lock.
- return rotation == NATURAL_ROTATION;
- }
-
- private void rescheduleRotationTimeout(final boolean reasonHover) {
- // May be called due to a new rotation proposal or a change in hover state
- if (reasonHover) {
- // Don't reschedule if a hide animator is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
- // Don't reschedule if not visible
- if (!mRotationButton.isVisible()) return;
- }
-
- // Stop any pending removal
- mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
- // Schedule timeout
- mMainThreadHandler.postDelayed(mRemoveRotationProposal,
- computeRotationProposalTimeout());
- }
-
- private int computeRotationProposalTimeout() {
- return mAccessibilityManager.getRecommendedTimeoutMillis(
- mHoveringRotationSuggestion ? 16000 : 5000,
- AccessibilityManager.FLAG_CONTENT_CONTROLS);
- }
-
- private boolean isRotateSuggestionIntroduced() {
- ContentResolver cr = mContext.getContentResolver();
- return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
- >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
- }
-
- private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
- // Get the number of accepted suggestions
- ContentResolver cr = mContext.getContentResolver();
- final int numSuggestions = Settings.Secure.getInt(cr,
- Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
-
- // Increment the number of accepted suggestions only if it would change intro mode
- if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
- Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
- numSuggestions + 1);
- }
- }
-
- private class TaskStackListenerImpl extends TaskStackChangeListener {
- // Invalidate any rotation suggestion on task change or activity orientation change
- // Note: all callbacks happen on main thread
-
- @Override
- public void onTaskStackChanged() {
- setRotateSuggestionButtonState(false /* visible */);
- }
-
- @Override
- public void onTaskRemoved(int taskId) {
- setRotateSuggestionButtonState(false /* visible */);
- }
-
- @Override
- public void onTaskMovedToFront(int taskId) {
- setRotateSuggestionButtonState(false /* visible */);
- }
-
- @Override
- public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
- // Only hide the icon if the top task changes its requestedOrientation
- // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
- Optional.ofNullable(ActivityManagerWrapper.getInstance())
- .map(ActivityManagerWrapper::getRunningTask)
- .ifPresent(a -> {
- if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
- });
- }
- }
-
- enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The rotation button was shown")
- ROTATION_SUGGESTION_SHOWN(206),
- @UiEvent(doc = "The rotation button was clicked")
- ROTATION_SUGGESTION_ACCEPTED(207);
-
- private final int mId;
- RotationButtonEvent(int id) {
- mId = id;
- }
- @Override public int getId() {
- return mId;
- }
- }
-}
-
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java b/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
index af5819a7ee..de6740d3b3 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
@@ -31,8 +31,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.taskbar.contextual.RotationButton;
-import com.android.launcher3.taskbar.contextual.RotationButtonController;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
import org.junit.Before;
import org.junit.Test;
@@ -55,8 +55,9 @@ public class NavigationBarRotationContextTest {
Context mTargetContext = InstrumentationRegistry.getTargetContext();
final View view = new View(mTargetContext);
RotationButton rotationButton = mock(RotationButton.class);
- mRotationButtonController = new RotationButtonController(mTargetContext, 0, 0);
- mRotationButtonController.setRotationButton(rotationButton);
+ mRotationButtonController = new RotationButtonController(mTargetContext, 0, 0, 0, 0, 0, 0,
+ () -> 0);
+ mRotationButtonController.setRotationButton(rotationButton, null);
// Due to a mockito issue, only spy the object after setting the initial state
mRotationButtonController = spy(mRotationButtonController);
final AnimatedVectorDrawable kbd = mock(AnimatedVectorDrawable.class);
@@ -85,7 +86,7 @@ public class NavigationBarRotationContextTest {
// No navigation bar should not call to set visibility state
mRotationButtonController.onBehaviorChanged(DEFAULT_DISPLAY,
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- mRotationButtonController.onTaskBarVisibilityChange(false /* showing */);
+ mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
@@ -100,7 +101,7 @@ public class NavigationBarRotationContextTest {
true /* visible */);
// Since rotation has changed rotation should be pending, show mButton when showing nav bar
- mRotationButtonController.onTaskBarVisibilityChange(true /* showing */);
+ mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
true /* visible */);
}
@@ -108,7 +109,7 @@ public class NavigationBarRotationContextTest {
@Test
public void testOnRotationProposalShowButton() {
// Navigation bar being visible should not call to set visibility state
- mRotationButtonController.onTaskBarVisibilityChange(true /* showing */);
+ mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
verify(mRotationButtonController, times(0))
.setRotateSuggestionButtonState(false /* visible */);
verify(mRotationButtonController, times(0))