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))