Move floating rotation button handling to Launcher am: 0288d2e8bb
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/16101502 Change-Id: I1ae7ea211aa8eafaaecbde5b2b356035fc254b6d
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user