Move floating rotation button handling to Launcher
Moves handling of floating rotation button when navigation bar is not created to the launcher. This button was not showing when taskbar is visible as it was initialized in navigation bar (which is not created for large screens). Bug: 200103245 Test: rotate phone when autorotate disabled on inner screen Test: showing rotate suggestion when gesture nav enabled/disabled Change-Id: I13dd555bcd811f1524be7ab9ad51b2b012b3b749
This commit is contained in:
parent
e6a04336eb
commit
0288d2e8bb
|
@ -30,6 +30,9 @@
|
|||
<color name="taskbar_stashed_handle_light_color">#EBffffff</color>
|
||||
<color name="taskbar_stashed_handle_dark_color">#99000000</color>
|
||||
|
||||
<color name="rotation_button_light_color">#FFF</color>
|
||||
<color name="rotation_button_dark_color">#99000000</color>
|
||||
|
||||
<!-- Gesture navigation tutorial -->
|
||||
<color name="gesture_tutorial_back_arrow_color">#FFFFFFFF</color>
|
||||
|
||||
|
|
|
@ -208,6 +208,9 @@
|
|||
<!-- Button text shown on a button on the tutorial skip dialog to exit the tutorial. [CHAR LIMIT=14] -->
|
||||
<string name="gesture_tutorial_action_button_label_skip">Skip</string>
|
||||
|
||||
<!-- Accessibility label for the rotation suggestion button -->
|
||||
<string name="accessibility_rotate_button">Rotate screen</string>
|
||||
|
||||
<!-- ******* Taskbar Edu ******* -->
|
||||
<!-- Accessibility text spoken when the taskbar education panel appears [CHAR_LIMIT=NONE] -->
|
||||
<string name="taskbar_edu_opened">Taskbar education appeared</string>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue