Merge "Refactor how we show rotation button in fully gestural mode." into qt-dev
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/start_menu_container"
|
||||
android:layout_width="@dimen/navigation_key_width"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"
|
||||
android:focusable="false"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
>
|
||||
<include layout="@layout/rotate_suggestion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible"
|
||||
/>
|
||||
<include layout="@layout/back"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible"
|
||||
/>
|
||||
</FrameLayout>
|
||||
@@ -331,7 +331,7 @@
|
||||
<!-- Nav bar button default ordering/layout -->
|
||||
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
|
||||
<string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
|
||||
<string name="config_navBarLayoutHandle" translatable="false">start_contextual[40AC];home_handle;ime_switcher[40AC]</string>
|
||||
<string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>
|
||||
|
||||
<bool name="quick_settings_show_full_alarm">false</bool>
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonView;
|
||||
|
||||
/** Containing logic for the rotation button on the physical left bottom corner of the screen. */
|
||||
public class FloatingRotationButton implements RotationButton {
|
||||
|
||||
private final Context mContext;
|
||||
private final WindowManager mWindowManager;
|
||||
private final KeyButtonView mKeyButtonView;
|
||||
private KeyButtonDrawable mKeyButtonDrawable;
|
||||
private boolean mIsShowing;
|
||||
private boolean mCanShow = true;
|
||||
|
||||
private RotationButtonController mRotationButtonController;
|
||||
|
||||
FloatingRotationButton(Context context) {
|
||||
mContext = context;
|
||||
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
|
||||
R.layout.rotate_suggestion, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotationButtonController(RotationButtonController rotationButtonController) {
|
||||
mRotationButtonController = rotationButtonController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getCurrentView() {
|
||||
return mKeyButtonView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean show() {
|
||||
if (!mCanShow || mIsShowing) {
|
||||
return false;
|
||||
}
|
||||
mIsShowing = true;
|
||||
int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
||||
float density = mContext.getResources().getDisplayMetrics().density;
|
||||
int diameter = (int) density * 48;
|
||||
int margin = (int) density * 4;
|
||||
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(diameter, diameter,
|
||||
margin, margin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
|
||||
lp.setTitle("FloatingRotationButton");
|
||||
switch (mWindowManager.getDefaultDisplay().getRotation()) {
|
||||
case Surface.ROTATION_0:
|
||||
lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
lp.gravity = Gravity.TOP | Gravity.RIGHT;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
lp.gravity = Gravity.TOP | Gravity.LEFT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
updateIcon();
|
||||
mWindowManager.addView(mKeyButtonView, lp);
|
||||
if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
|
||||
mKeyButtonDrawable.resetAnimation();
|
||||
mKeyButtonDrawable.startAnimation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hide() {
|
||||
if (!mIsShowing) {
|
||||
return false;
|
||||
}
|
||||
mWindowManager.removeViewImmediate(mKeyButtonView);
|
||||
mRotationButtonController.cleanUp();
|
||||
mIsShowing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return mIsShowing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIcon() {
|
||||
if (!mIsShowing) {
|
||||
return;
|
||||
}
|
||||
mKeyButtonDrawable = getImageDrawable();
|
||||
mKeyButtonView.setImageDrawable(mKeyButtonDrawable);
|
||||
mKeyButtonDrawable.setCallback(mKeyButtonView);
|
||||
if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
|
||||
mKeyButtonDrawable.resetAnimation();
|
||||
mKeyButtonDrawable.startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(View.OnClickListener onClickListener) {
|
||||
mKeyButtonView.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnHoverListener(View.OnHoverListener onHoverListener) {
|
||||
mKeyButtonView.setOnHoverListener(onHoverListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyButtonDrawable getImageDrawable() {
|
||||
Context context = new ContextThemeWrapper(mContext.getApplicationContext(),
|
||||
mRotationButtonController.getStyleRes());
|
||||
return KeyButtonDrawable.create(context, R.drawable.ic_sysbar_rotate_button,
|
||||
false /* shadow */, true /* hasOvalBg */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDarkIntensity(float darkIntensity) {
|
||||
mKeyButtonView.setDarkIntensity(darkIntensity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanShowRotationButton(boolean canShow) {
|
||||
mCanShow = canShow;
|
||||
if (!mCanShow) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
|
||||
@Override
|
||||
public void onQuickStepStarted() {
|
||||
// Use navbar dragging as a signal to hide the rotate button
|
||||
mNavigationBarView.getRotateSuggestionButton().setRotateSuggestionButtonState(false);
|
||||
mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
|
||||
|
||||
// Hide the notifications panel when quick step starts
|
||||
mStatusBar.collapsePanel(true /* animate */);
|
||||
@@ -333,16 +333,16 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
|
||||
|
||||
// Currently there is no accelerometer sensor on non-default display.
|
||||
if (mIsOnDefaultDisplay) {
|
||||
final RotationContextButton rotationButton =
|
||||
mNavigationBarView.getRotateSuggestionButton();
|
||||
rotationButton.setListener(mRotationButtonListener);
|
||||
rotationButton.addRotationCallback(mRotationWatcher);
|
||||
mNavigationBarView.getRotateSuggestionButton().setListener(mRotationButtonListener);
|
||||
|
||||
final RotationButtonController rotationButtonController =
|
||||
mNavigationBarView.getRotationButtonController();
|
||||
rotationButtonController.addRotationCallback(mRotationWatcher);
|
||||
|
||||
// Reset user rotation pref to match that of the WindowManager if starting in locked
|
||||
// mode. This will automatically happen when switching from auto-rotate to locked mode.
|
||||
if (display != null && rotationButton.isRotationLocked()) {
|
||||
final int winRotation = display.getRotation();
|
||||
rotationButton.setRotationLockedAtAngle(winRotation);
|
||||
if (display != null && rotationButtonController.isRotationLocked()) {
|
||||
rotationButtonController.setRotationLockedAtAngle(display.getRotation());
|
||||
}
|
||||
} else {
|
||||
mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
|
||||
@@ -458,34 +458,34 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
|
||||
if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
|
||||
|
||||
updateSystemUiStateFlags(-1);
|
||||
mNavigationBarView.getRotateSuggestionButton()
|
||||
.onNavigationBarWindowVisibilityChange(isNavBarWindowVisible());
|
||||
mNavigationBarView.getRotationButtonController().onNavigationBarWindowVisibilityChange(
|
||||
isNavBarWindowVisible());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationProposal(final int rotation, boolean isValid) {
|
||||
final int winRotation = mNavigationBarView.getDisplay().getRotation();
|
||||
final boolean rotateSuggestionsDisabled = RotationContextButton
|
||||
final boolean rotateSuggestionsDisabled = RotationButtonController
|
||||
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
|
||||
final RotationButtonController rotationButtonController =
|
||||
mNavigationBarView.getRotationButtonController();
|
||||
final RotationButton rotationButton = rotationButtonController.getRotationButton();
|
||||
|
||||
if (RotationContextButton.DEBUG_ROTATION) {
|
||||
Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
|
||||
+ ", winRotation=" + Surface.rotationToString(winRotation)
|
||||
+ ", isValid=" + isValid + ", mNavBarWindowState="
|
||||
+ StatusBarManager.windowStateToString(mNavigationBarWindowState)
|
||||
+ ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
|
||||
+ ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null" :
|
||||
mNavigationBarView.getRotateSuggestionButton().isVisible()));
|
||||
+ ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null"
|
||||
: rotationButton.isVisible()));
|
||||
}
|
||||
|
||||
// Respect the disabled flag, no need for action as flag change callback will handle hiding
|
||||
if (rotateSuggestionsDisabled) return;
|
||||
|
||||
View rotationButton = mNavigationBarView.getRotateSuggestionButton().getCurrentView();
|
||||
if (rotationButton != null && rotationButton.isAttachedToWindow()) {
|
||||
mNavigationBarView.getRotateSuggestionButton()
|
||||
.onRotationProposal(rotation, winRotation, isValid);
|
||||
}
|
||||
rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
|
||||
}
|
||||
|
||||
/** Restores the System UI flags saved state to {@link NavigationBarFragment}. */
|
||||
@@ -593,7 +593,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
|
||||
private void setDisabled2Flags(int state2) {
|
||||
// Method only called on change of disable2 flags
|
||||
if (mNavigationBarView != null) {
|
||||
mNavigationBarView.getRotateSuggestionButton().onDisable2FlagChanged(state2);
|
||||
mNavigationBarView.getRotationButtonController().onDisable2FlagChanged(state2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,8 +862,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
|
||||
boolean[] feedbackEnabled = new boolean[1];
|
||||
int a11yFlags = getA11yButtonState(feedbackEnabled);
|
||||
|
||||
mNavigationBarView.getRotateSuggestionButton()
|
||||
.setAccessibilityFeedbackEnabled(feedbackEnabled[0]);
|
||||
mNavigationBarView.getRotationButtonController().setAccessibilityFeedbackEnabled(
|
||||
feedbackEnabled[0]);
|
||||
|
||||
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
|
||||
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
|
||||
|
||||
@@ -63,7 +63,6 @@ public class NavigationBarInflaterView extends FrameLayout
|
||||
public static final String RIGHT = "right";
|
||||
public static final String CONTEXTUAL = "contextual";
|
||||
public static final String IME_SWITCHER = "ime_switcher";
|
||||
public static final String START_CONTEXTUAL = "start_contextual";
|
||||
|
||||
public static final String GRAVITY_SEPARATOR = ";";
|
||||
public static final String BUTTON_SEPARATOR = ",";
|
||||
@@ -395,8 +394,6 @@ public class NavigationBarInflaterView extends FrameLayout
|
||||
v = inflater.inflate(R.layout.home_handle, parent, false);
|
||||
} else if (IME_SWITCHER.equals(button)) {
|
||||
v = inflater.inflate(R.layout.ime_switcher, parent, false);
|
||||
} else if (START_CONTEXTUAL.equals(button)) {
|
||||
v = inflater.inflate(R.layout.start_contextual, parent, false);
|
||||
} else if (button.startsWith(KEY)) {
|
||||
String uri = extractImage(button);
|
||||
int code = extractKeycode(button);
|
||||
|
||||
@@ -173,6 +173,7 @@ public final class NavigationBarTransitions extends BarTransitions implements
|
||||
for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
|
||||
buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
|
||||
}
|
||||
mView.getRotationButtonController().setDarkIntensity(darkIntensity);
|
||||
if (mAutoDim) {
|
||||
applyLightsOut(false, true);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_H
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
|
||||
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
|
||||
|
||||
import android.animation.LayoutTransition;
|
||||
@@ -130,7 +131,6 @@ public class NavigationBarView extends FrameLayout implements
|
||||
private boolean mImeVisible;
|
||||
|
||||
private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
|
||||
private final ContextualButtonGroup mStartContextualButtonGroup;
|
||||
private final ContextualButtonGroup mContextualButtonGroup;
|
||||
private Configuration mConfiguration;
|
||||
private Configuration mTmpLastConfiguration;
|
||||
@@ -138,6 +138,8 @@ public class NavigationBarView extends FrameLayout implements
|
||||
private NavigationBarInflaterView mNavigationInflaterView;
|
||||
private RecentsOnboarding mRecentsOnboarding;
|
||||
private NotificationPanelView mPanelView;
|
||||
private FloatingRotationButton mFloatingRotationButton;
|
||||
private RotationButtonController mRotationButtonController;
|
||||
|
||||
private NavBarTintController mTintController;
|
||||
|
||||
@@ -232,24 +234,12 @@ public class NavigationBarView extends FrameLayout implements
|
||||
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
|
||||
// When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
|
||||
// gestural mode, the entire nav bar should be touchable.
|
||||
if (!QuickStepContract.isGesturalMode(mNavBarMode) || mImeVisible) {
|
||||
if (!isGesturalMode(mNavBarMode) || mImeVisible) {
|
||||
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
|
||||
return;
|
||||
}
|
||||
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
||||
RotationContextButton rotationContextButton = getRotateSuggestionButton();
|
||||
// If the rotate suggestion button is not visible in fully gestural mode, the entire nav bar
|
||||
// is not touchable so that the app underneath can be clicked through.
|
||||
if (rotationContextButton.getVisibility() != VISIBLE) {
|
||||
info.touchableRegion.setEmpty();
|
||||
} else {
|
||||
// Set the rotate suggestion button area to be touchable.
|
||||
rotationContextButton.getCurrentView().getLocationInWindow(mTmpPosition);
|
||||
Rect rect = new Rect(mTmpPosition[0], mTmpPosition[1],
|
||||
mTmpPosition[0] + mRotationButtonBounds.width(),
|
||||
mTmpPosition[1] + mRotationButtonBounds.height());
|
||||
info.touchableRegion.union(rect);
|
||||
}
|
||||
info.touchableRegion.setEmpty();
|
||||
};
|
||||
|
||||
public NavigationBarView(Context context, AttributeSet attrs) {
|
||||
@@ -258,15 +248,14 @@ public class NavigationBarView extends FrameLayout implements
|
||||
mIsVertical = false;
|
||||
mLongClickableAccessibilityButton = false;
|
||||
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
|
||||
boolean isGesturalMode = QuickStepContract.isGesturalMode(mNavBarMode);
|
||||
boolean isGesturalMode = isGesturalMode(mNavBarMode);
|
||||
|
||||
// Set up the context group of buttons
|
||||
mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
|
||||
final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
|
||||
R.drawable.ic_ime_switcher_default);
|
||||
final RotationContextButton rotateSuggestionButton = new RotationContextButton(
|
||||
R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button, getContext(),
|
||||
R.style.RotateButtonCCWStart90);
|
||||
R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
|
||||
final ContextualButton accessibilityButton =
|
||||
new ContextualButton(R.id.accessibility_button,
|
||||
R.drawable.ic_sysbar_accessibility_button);
|
||||
@@ -278,13 +267,12 @@ public class NavigationBarView extends FrameLayout implements
|
||||
|
||||
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
|
||||
mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
|
||||
mFloatingRotationButton = new FloatingRotationButton(context);
|
||||
mRotationButtonController = new RotationButtonController(context,
|
||||
R.style.RotateButtonCCWStart90,
|
||||
isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
|
||||
|
||||
final ContextualButton backButton = new ContextualButton(R.id.back, 0);
|
||||
mStartContextualButtonGroup = new ContextualButtonGroup(R.id.start_menu_container);
|
||||
if (isGesturalMode) {
|
||||
mStartContextualButtonGroup.addButton(rotateSuggestionButton);
|
||||
}
|
||||
mStartContextualButtonGroup.addButton(backButton);
|
||||
|
||||
mConfiguration = new Configuration();
|
||||
mTmpLastConfiguration = new Configuration();
|
||||
@@ -301,7 +289,6 @@ public class NavigationBarView extends FrameLayout implements
|
||||
mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
|
||||
mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
|
||||
mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
|
||||
mButtonDispatchers.put(R.id.start_menu_container, mStartContextualButtonGroup);
|
||||
mDeadZone = new DeadZone(this);
|
||||
|
||||
mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
|
||||
@@ -390,6 +377,14 @@ public class NavigationBarView extends FrameLayout implements
|
||||
return mCurrentView;
|
||||
}
|
||||
|
||||
public RotationButtonController getRotationButtonController() {
|
||||
return mRotationButtonController;
|
||||
}
|
||||
|
||||
public FloatingRotationButton getFloatingRotationButton() {
|
||||
return mFloatingRotationButton;
|
||||
}
|
||||
|
||||
public ButtonDispatcher getRecentsButton() {
|
||||
return mButtonDispatchers.get(R.id.recent_apps);
|
||||
}
|
||||
@@ -414,10 +409,6 @@ public class NavigationBarView extends FrameLayout implements
|
||||
return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
|
||||
}
|
||||
|
||||
public ContextualButtonGroup getStartContextualButtonGroup() {
|
||||
return mStartContextualButtonGroup;
|
||||
}
|
||||
|
||||
public ButtonDispatcher getHomeHandle() {
|
||||
return mButtonDispatchers.get(R.id.home_handle);
|
||||
}
|
||||
@@ -454,7 +445,6 @@ public class NavigationBarView extends FrameLayout implements
|
||||
if (densityChange || dirChange) {
|
||||
mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
|
||||
mContextualButtonGroup.updateIcons();
|
||||
mStartContextualButtonGroup.updateIcons();
|
||||
}
|
||||
if (orientationChange || densityChange || dirChange) {
|
||||
mBackIcon = getBackDrawable();
|
||||
@@ -490,7 +480,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
return;
|
||||
}
|
||||
|
||||
if (QuickStepContract.isGesturalMode(mNavBarMode)) {
|
||||
if (isGesturalMode(mNavBarMode)) {
|
||||
drawable.setRotation(degrees);
|
||||
return;
|
||||
}
|
||||
@@ -561,6 +551,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
mTransitionListener.onBackAltCleared();
|
||||
}
|
||||
mImeVisible = visible;
|
||||
mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
|
||||
}
|
||||
|
||||
public void setDisabledFlags(int disabledFlags) {
|
||||
@@ -603,7 +594,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
|
||||
mBarTransitions.reapplyDarkIntensity();
|
||||
|
||||
boolean disableHome = QuickStepContract.isGesturalMode(mNavBarMode)
|
||||
boolean disableHome = isGesturalMode(mNavBarMode)
|
||||
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
|
||||
|
||||
// TODO(b/113914868): investigation log for disappearing home button
|
||||
@@ -613,7 +604,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
// Always disable recents when alternate car mode UI is active and for secondary displays.
|
||||
boolean disableRecent = isRecentsButtonDisabled();
|
||||
|
||||
boolean disableBack = !useAltBack && (QuickStepContract.isGesturalMode(mNavBarMode)
|
||||
boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
|
||||
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
|
||||
|
||||
// When screen pinning, don't hide back and home when connected service or back and
|
||||
@@ -641,7 +632,6 @@ public class NavigationBarView extends FrameLayout implements
|
||||
}
|
||||
|
||||
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
|
||||
mStartContextualButtonGroup.setButtonVisibility(R.id.back, !disableBack);
|
||||
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
|
||||
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
@@ -782,7 +772,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
|
||||
// Color adaption is tied with showing home handle, only avaliable if visible
|
||||
mTintController.onNavigationModeChanged(mNavBarMode);
|
||||
if (QuickStepContract.isGesturalMode(mNavBarMode)) {
|
||||
if (isGesturalMode(mNavBarMode)) {
|
||||
mTintController.start();
|
||||
} else {
|
||||
mTintController.stop();
|
||||
@@ -939,7 +929,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
"onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
|
||||
|
||||
final boolean newVertical = w > 0 && h > w
|
||||
&& !QuickStepContract.isGesturalMode(mNavBarMode);
|
||||
&& !isGesturalMode(mNavBarMode);
|
||||
if (newVertical != mIsVertical) {
|
||||
mIsVertical = newVertical;
|
||||
if (DEBUG) {
|
||||
@@ -950,7 +940,7 @@ public class NavigationBarView extends FrameLayout implements
|
||||
notifyVerticalChangedListener(newVertical);
|
||||
}
|
||||
|
||||
if (QuickStepContract.isGesturalMode(mNavBarMode)) {
|
||||
if (isGesturalMode(mNavBarMode)) {
|
||||
// Update the nav bar background to match the height of the visible nav bar
|
||||
int height = mIsVertical
|
||||
? getResources().getDimensionPixelSize(
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
|
||||
/** Interface of a rotation button that interacts {@link RotationButtonController}. */
|
||||
interface RotationButton {
|
||||
void setRotationButtonController(RotationButtonController rotationButtonController);
|
||||
View getCurrentView();
|
||||
boolean show();
|
||||
boolean hide();
|
||||
boolean isVisible();
|
||||
void updateIcon();
|
||||
void setOnClickListener(View.OnClickListener onClickListener);
|
||||
void setOnHoverListener(View.OnHoverListener onHoverListener);
|
||||
KeyButtonDrawable getImageDrawable();
|
||||
void setDarkIntensity(float darkIntensity);
|
||||
default void setCanShowRotationButton(boolean canShow) {}
|
||||
default boolean acceptRotationProposal() {
|
||||
return getCurrentView() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.StyleRes;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.view.IRotationWatcher.Stub;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
import com.android.systemui.statusbar.policy.RotationLockController;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Contains logic that deals with showing a rotate suggestion button with animation. */
|
||||
public class 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 MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
|
||||
private final ViewRippler mViewRippler = new ViewRippler();
|
||||
|
||||
private @StyleRes int mStyleRes;
|
||||
private int mLastRotationSuggestion;
|
||||
private boolean mPendingRotationSuggestion;
|
||||
private boolean mHoveringRotationSuggestion;
|
||||
private RotationLockController mRotationLockController;
|
||||
private TaskStackListenerImpl mTaskStackListener;
|
||||
private Consumer<Integer> mRotWatcherListener;
|
||||
private boolean mIsNavigationBarShowing;
|
||||
|
||||
private final Runnable mRemoveRotationProposal =
|
||||
() -> setRotateSuggestionButtonState(false /* visible */);
|
||||
private final Runnable mCancelPendingRotationProposal =
|
||||
() -> mPendingRotationSuggestion = false;
|
||||
private Animator mRotateHideAnimator;
|
||||
private boolean mAccessibilityFeedbackEnabled;
|
||||
|
||||
private final Context mContext;
|
||||
private final RotationButton mRotationButton;
|
||||
|
||||
private final Stub mRotationWatcher = new Stub() {
|
||||
@Override
|
||||
public void onRotationChanged(final int rotation) throws RemoteException {
|
||||
if (mRotationButton.getCurrentView() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need this to be scheduled as early as possible to beat the redrawing of
|
||||
// window in response to the orientation change.
|
||||
Handler h = mRotationButton.getCurrentView().getHandler();
|
||||
Message msg = Message.obtain(h, () -> {
|
||||
// If the screen rotation changes while locked, potentially update lock to flow with
|
||||
// new screen rotation and hide any showing suggestions.
|
||||
if (mRotationLockController.isRotationLocked()) {
|
||||
if (shouldOverrideUserLockPrefs(rotation)) {
|
||||
setRotationLockedAtAngle(rotation);
|
||||
}
|
||||
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
|
||||
}
|
||||
|
||||
if (mRotWatcherListener != null) {
|
||||
mRotWatcherListener.accept(rotation);
|
||||
}
|
||||
});
|
||||
msg.setAsynchronous(true);
|
||||
h.sendMessageAtFrontOfQueue(msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
RotationButtonController(Context context, @StyleRes int style, RotationButton rotationButton) {
|
||||
mContext = context;
|
||||
mRotationButton = rotationButton;
|
||||
mRotationButton.setRotationButtonController(this);
|
||||
|
||||
mStyleRes = style;
|
||||
mIsNavigationBarShowing = true;
|
||||
mRotationLockController = Dependency.get(RotationLockController.class);
|
||||
|
||||
// Register the task stack listener
|
||||
mTaskStackListener = new TaskStackListenerImpl();
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
|
||||
mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
|
||||
mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
|
||||
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
void cleanUp() {
|
||||
// Unregister the task stack listener
|
||||
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
|
||||
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
void addRotationCallback(Consumer<Integer> watcher) {
|
||||
mRotWatcherListener = watcher;
|
||||
}
|
||||
|
||||
void setAccessibilityFeedbackEnabled(boolean flag) {
|
||||
mAccessibilityFeedbackEnabled = flag;
|
||||
}
|
||||
|
||||
void setRotationLockedAtAngle(int rotationSuggestion) {
|
||||
mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
|
||||
}
|
||||
|
||||
public boolean isRotationLocked() {
|
||||
return mRotationLockController.isRotationLocked();
|
||||
}
|
||||
|
||||
void setRotateSuggestionButtonState(boolean visible) {
|
||||
setRotateSuggestionButtonState(visible, false /* force */);
|
||||
}
|
||||
|
||||
void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
|
||||
// At any point the 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 KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable();
|
||||
if (currentDrawable == null) return;
|
||||
|
||||
// Clear any pending suggestion flag as it has either been nullified or is being shown
|
||||
mPendingRotationSuggestion = false;
|
||||
view.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
|
||||
if (currentDrawable.canAnimate()) {
|
||||
currentDrawable.resetAnimation();
|
||||
currentDrawable.startAnimation();
|
||||
}
|
||||
|
||||
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(Interpolators.LINEAR);
|
||||
fadeOut.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mRotationButton.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mRotateHideAnimator = fadeOut;
|
||||
fadeOut.start();
|
||||
}
|
||||
}
|
||||
|
||||
void setDarkIntensity(float darkIntensity) {
|
||||
mRotationButton.setDarkIntensity(darkIntensity);
|
||||
}
|
||||
|
||||
void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
|
||||
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;
|
||||
}
|
||||
|
||||
final View currentView = mRotationButton.getCurrentView();
|
||||
|
||||
// If window rotation matches suggested rotation, remove any current suggestions
|
||||
if (rotation == windowRotation) {
|
||||
if (currentView != null) {
|
||||
currentView.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 = isRotationAnimationCCW(windowRotation, rotation);
|
||||
int style;
|
||||
if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
|
||||
style = rotationCCW ? R.style.RotateButtonCCWStart90 : R.style.RotateButtonCWStart90;
|
||||
} else { // 90 or 270
|
||||
style = rotationCCW ? R.style.RotateButtonCCWStart0 : R.style.RotateButtonCWStart0;
|
||||
}
|
||||
mStyleRes = style;
|
||||
mRotationButton.updateIcon();
|
||||
|
||||
if (mIsNavigationBarShowing) {
|
||||
// The navbar is visible 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;
|
||||
if (currentView != null) {
|
||||
currentView.removeCallbacks(mCancelPendingRotationProposal);
|
||||
currentView.postDelayed(mCancelPendingRotationProposal,
|
||||
NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onDisable2FlagChanged(int state2) {
|
||||
final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
|
||||
if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
|
||||
}
|
||||
|
||||
void onNavigationBarWindowVisibilityChange(boolean showing) {
|
||||
if (mIsNavigationBarShowing != showing) {
|
||||
mIsNavigationBarShowing = showing;
|
||||
|
||||
// If the navbar is visible, show the rotate button if there's a pending suggestion
|
||||
if (showing && mPendingRotationSuggestion) {
|
||||
showAndLogRotationSuggestion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@StyleRes int getStyleRes() {
|
||||
return mStyleRes;
|
||||
}
|
||||
|
||||
RotationButton getRotationButton() {
|
||||
return mRotationButton;
|
||||
}
|
||||
|
||||
private void onRotateSuggestionClick(View v) {
|
||||
mMetricsLogger.action(MetricsEvent.ACTION_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 */);
|
||||
if (mRotationButton.getCurrentView() != null) {
|
||||
mRotationButton.getCurrentView().removeCallbacks(mRemoveRotationProposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void showAndLogRotationSuggestion() {
|
||||
setRotateSuggestionButtonState(true /* visible */);
|
||||
rescheduleRotationTimeout(false /* reasonHover */);
|
||||
mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
|
||||
}
|
||||
|
||||
private boolean shouldOverrideUserLockPrefs(final int rotation) {
|
||||
// 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 boolean isRotationAnimationCCW(int from, int to) {
|
||||
// All 180deg WM rotation animations are CCW, match that
|
||||
if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
|
||||
if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
|
||||
if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
|
||||
if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
|
||||
if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
|
||||
if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
|
||||
if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
|
||||
if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
|
||||
return false; // Default
|
||||
}
|
||||
|
||||
private void rescheduleRotationTimeout(final boolean reasonHover) {
|
||||
if (mRotationButton.getCurrentView() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
mRotationButton.getCurrentView().removeCallbacks(mRemoveRotationProposal);
|
||||
// Schedule timeout
|
||||
mRotationButton.getCurrentView().postDelayed(mRemoveRotationProposal,
|
||||
computeRotationProposalTimeout());
|
||||
}
|
||||
|
||||
private int computeRotationProposalTimeout() {
|
||||
if (mAccessibilityFeedbackEnabled) return 10000;
|
||||
if (mHoveringRotationSuggestion) return 8000;
|
||||
return 5000;
|
||||
}
|
||||
|
||||
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 */);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewRippler {
|
||||
private static final int RIPPLE_OFFSET_MS = 50;
|
||||
private static final int RIPPLE_INTERVAL_MS = 2000;
|
||||
private View mRoot;
|
||||
|
||||
public void start(View root) {
|
||||
stop(); // Stop any pending ripple animations
|
||||
|
||||
mRoot = root;
|
||||
|
||||
// Schedule pending ripples, offset the 1st to avoid problems with visibility change
|
||||
mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mRoot != null) mRoot.removeCallbacks(mRipple);
|
||||
}
|
||||
|
||||
private final Runnable mRipple = new Runnable() {
|
||||
@Override
|
||||
public void run() { // Cause the ripple to fire via false presses
|
||||
if (!mRoot.isAttachedToWindow()) return;
|
||||
mRoot.setPressed(true /* pressed */);
|
||||
mRoot.setPressed(false /* pressed */);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -18,281 +18,30 @@ package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
|
||||
|
||||
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.IdRes;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.StyleRes;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.IRotationWatcher.Stub;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
|
||||
import com.android.systemui.statusbar.policy.RotationLockController;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Containing logic for the rotation button in nav bar. */
|
||||
public class RotationContextButton extends ContextualButton implements
|
||||
NavigationModeController.ModeChangedListener {
|
||||
NavigationModeController.ModeChangedListener, RotationButton {
|
||||
public static final boolean DEBUG_ROTATION = false;
|
||||
|
||||
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 @StyleRes int mStyleRes;
|
||||
|
||||
private int mLastRotationSuggestion;
|
||||
private boolean mPendingRotationSuggestion;
|
||||
private boolean mHoveringRotationSuggestion;
|
||||
private RotationLockController mRotationLockController;
|
||||
private TaskStackListenerImpl mTaskStackListener;
|
||||
private Consumer<Integer> mRotWatcherListener;
|
||||
private boolean mIsNavigationBarShowing;
|
||||
|
||||
private final Runnable mRemoveRotationProposal =
|
||||
() -> setRotateSuggestionButtonState(false /* visible */);
|
||||
private final Runnable mCancelPendingRotationProposal =
|
||||
() -> mPendingRotationSuggestion = false;
|
||||
private Animator mRotateHideAnimator;
|
||||
private boolean mAccessibilityFeedbackEnabled;
|
||||
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
|
||||
private RotationButtonController mRotationButtonController;
|
||||
|
||||
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
|
||||
private final ViewRippler mViewRippler = new ViewRippler();
|
||||
|
||||
private final Stub mRotationWatcher = new Stub() {
|
||||
@Override
|
||||
public void onRotationChanged(final int rotation) throws RemoteException {
|
||||
if (getCurrentView() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need this to be scheduled as early as possible to beat the redrawing of
|
||||
// window in response to the orientation change.
|
||||
Handler h = getCurrentView().getHandler();
|
||||
Message msg = Message.obtain(h, () -> {
|
||||
// If the screen rotation changes while locked, potentially update lock to flow with
|
||||
// new screen rotation and hide any showing suggestions.
|
||||
if (mRotationLockController.isRotationLocked()) {
|
||||
if (shouldOverrideUserLockPrefs(rotation)) {
|
||||
setRotationLockedAtAngle(rotation);
|
||||
}
|
||||
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
|
||||
}
|
||||
|
||||
if (mRotWatcherListener != null) {
|
||||
mRotWatcherListener.accept(rotation);
|
||||
}
|
||||
});
|
||||
msg.setAsynchronous(true);
|
||||
h.sendMessageAtFrontOfQueue(msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId,
|
||||
@NonNull Context context, @StyleRes int style) {
|
||||
public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
|
||||
super(buttonResId, iconResId);
|
||||
|
||||
mStyleRes = style;
|
||||
mIsNavigationBarShowing = true;
|
||||
mRotationLockController = Dependency.get(RotationLockController.class);
|
||||
|
||||
// Register the task stack listener
|
||||
mTaskStackListener = new TaskStackListenerImpl();
|
||||
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
|
||||
setOnClickListener(this::onRotateSuggestionClick);
|
||||
setOnHoverListener(this::onRotateSuggestionHover);
|
||||
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.watchRotation(mRotationWatcher, context.getDisplay().getDisplayId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
public void addRotationCallback(Consumer<Integer> watcher) {
|
||||
mRotWatcherListener = watcher;
|
||||
}
|
||||
|
||||
public void setRotateSuggestionButtonState(boolean visible) {
|
||||
setRotateSuggestionButtonState(visible, false /* force */);
|
||||
}
|
||||
|
||||
public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
|
||||
// At any point the 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 && !isVisible()) return;
|
||||
|
||||
final View view = getCurrentView();
|
||||
if (view == null) return;
|
||||
|
||||
final KeyButtonDrawable currentDrawable = getImageDrawable();
|
||||
if (currentDrawable == null) return;
|
||||
|
||||
// Clear any pending suggestion flag as it has either been nullified or is being shown
|
||||
mPendingRotationSuggestion = false;
|
||||
view.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
|
||||
if (currentDrawable.canAnimate()) {
|
||||
currentDrawable.resetAnimation();
|
||||
currentDrawable.startAnimation();
|
||||
}
|
||||
|
||||
if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
|
||||
|
||||
// Set visibility unless a11y service is active.
|
||||
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();
|
||||
}
|
||||
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(Interpolators.LINEAR);
|
||||
fadeOut.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
mRotateHideAnimator = fadeOut;
|
||||
fadeOut.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAccessibilityFeedbackEnabled(boolean flag) {
|
||||
mAccessibilityFeedbackEnabled = flag;
|
||||
}
|
||||
|
||||
public void setRotationLockedAtAngle(int rotationSuggestion) {
|
||||
mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
|
||||
}
|
||||
|
||||
public boolean isRotationLocked() {
|
||||
return mRotationLockController.isRotationLocked();
|
||||
}
|
||||
|
||||
public void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
|
||||
// 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) {
|
||||
if (getCurrentView() != null) {
|
||||
getCurrentView().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 = isRotationAnimationCCW(windowRotation, rotation);
|
||||
int style;
|
||||
if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
|
||||
style = rotationCCW ? R.style.RotateButtonCCWStart90 : R.style.RotateButtonCWStart90;
|
||||
} else { // 90 or 270
|
||||
style = rotationCCW ? R.style.RotateButtonCCWStart0 : R.style.RotateButtonCWStart0;
|
||||
}
|
||||
mStyleRes = style;
|
||||
updateIcon();
|
||||
|
||||
if (mIsNavigationBarShowing) {
|
||||
// The navbar is visible 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;
|
||||
if (getCurrentView() != null) {
|
||||
getCurrentView().removeCallbacks(mCancelPendingRotationProposal);
|
||||
getCurrentView().postDelayed(mCancelPendingRotationProposal,
|
||||
NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable2FlagChanged(int state2) {
|
||||
final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
|
||||
if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
|
||||
}
|
||||
|
||||
public void onNavigationBarWindowVisibilityChange(boolean showing) {
|
||||
if (mIsNavigationBarShowing != showing) {
|
||||
mIsNavigationBarShowing = showing;
|
||||
|
||||
// If the navbar is visible, show the rotate button if there's a pending suggestion
|
||||
if (showing && mPendingRotationSuggestion) {
|
||||
showAndLogRotationSuggestion();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void setRotationButtonController(RotationButtonController rotationButtonController) {
|
||||
mRotationButtonController = rotationButtonController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -309,116 +58,15 @@ public class RotationContextButton extends ContextualButton implements
|
||||
|
||||
@Override
|
||||
protected KeyButtonDrawable getNewDrawable() {
|
||||
Context context = new ContextThemeWrapper(getContext().getApplicationContext(), mStyleRes);
|
||||
Context context = new ContextThemeWrapper(getContext().getApplicationContext(),
|
||||
mRotationButtonController.getStyleRes());
|
||||
return KeyButtonDrawable.create(context, mIconResId, false /* shadow */,
|
||||
QuickStepContract.isGesturalMode(mNavBarMode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// Unregister the task stack listener
|
||||
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
|
||||
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private void onRotateSuggestionClick(View v) {
|
||||
mMetricsLogger.action(MetricsEvent.ACTION_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 */);
|
||||
if (getCurrentView() != null) {
|
||||
getCurrentView().removeCallbacks(mRemoveRotationProposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void showAndLogRotationSuggestion() {
|
||||
setRotateSuggestionButtonState(true /* visible */);
|
||||
rescheduleRotationTimeout(false /* reasonHover */);
|
||||
mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
|
||||
}
|
||||
|
||||
private boolean shouldOverrideUserLockPrefs(final int rotation) {
|
||||
// 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 boolean isRotationAnimationCCW(int from, int to) {
|
||||
// All 180deg WM rotation animations are CCW, match that
|
||||
if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
|
||||
if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
|
||||
if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
|
||||
if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
|
||||
if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
|
||||
if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
|
||||
if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
|
||||
if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
|
||||
if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
|
||||
return false; // Default
|
||||
}
|
||||
|
||||
private void rescheduleRotationTimeout(final boolean reasonHover) {
|
||||
if (getCurrentView() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 (!isVisible()) return;
|
||||
}
|
||||
|
||||
// Stop any pending removal
|
||||
getCurrentView().removeCallbacks(mRemoveRotationProposal);
|
||||
// Schedule timeout
|
||||
getCurrentView().postDelayed(mRemoveRotationProposal, computeRotationProposalTimeout());
|
||||
}
|
||||
|
||||
private int computeRotationProposalTimeout() {
|
||||
if (mAccessibilityFeedbackEnabled) return 10000;
|
||||
if (mHoveringRotationSuggestion) return 8000;
|
||||
return 5000;
|
||||
}
|
||||
|
||||
private boolean isRotateSuggestionIntroduced() {
|
||||
ContentResolver cr = getContext().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 = getContext().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);
|
||||
}
|
||||
mRotationButtonController.cleanUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -426,66 +74,9 @@ public class RotationContextButton extends ContextualButton implements
|
||||
mNavBarMode = mode;
|
||||
}
|
||||
|
||||
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 */);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewRippler {
|
||||
private static final int RIPPLE_OFFSET_MS = 50;
|
||||
private static final int RIPPLE_INTERVAL_MS = 2000;
|
||||
private View mRoot;
|
||||
|
||||
public void start(View root) {
|
||||
stop(); // Stop any pending ripple animations
|
||||
|
||||
mRoot = root;
|
||||
|
||||
// Schedule pending ripples, offset the 1st to avoid problems with visibility change
|
||||
mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
|
||||
mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mRoot != null) mRoot.removeCallbacks(mRipple);
|
||||
}
|
||||
|
||||
private final Runnable mRipple = new Runnable() {
|
||||
@Override
|
||||
public void run() { // Cause the ripple to fire via false presses
|
||||
if (!mRoot.isAttachedToWindow()) return;
|
||||
mRoot.setPressed(true /* pressed */);
|
||||
mRoot.setPressed(false /* pressed */);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public boolean acceptRotationProposal() {
|
||||
View currentView = getCurrentView();
|
||||
return currentView != null && currentView.isAttachedToWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +271,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
|
||||
when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class));
|
||||
when(view.getLightTransitionsController()).thenReturn(
|
||||
mock(LightBarTransitionsController.class));
|
||||
when(view.getRotationButtonController()).thenReturn(
|
||||
mock(RotationButtonController.class));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
|
||||
public final SysuiTestableContext mContext = new SysuiTestableContext(
|
||||
InstrumentationRegistry.getContext(), getLeakCheck());
|
||||
private final TestableDependency mDependency = new TestableDependency(mContext);
|
||||
private RotationContextButton mButton;
|
||||
private RotationButtonController mRotationButtonController;
|
||||
private RotationButton mRotationButton;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -59,50 +60,66 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
|
||||
mDependency.injectMockDependency(RotationLockController.class);
|
||||
|
||||
final View view = new View(mContext);
|
||||
mButton = spy(new RotationContextButton(RES_UNDEF, RES_UNDEF, mContext, RES_UNDEF));
|
||||
mRotationButton = mock(RotationButton.class);
|
||||
mRotationButtonController = spy(
|
||||
new RotationButtonController(mContext, RES_UNDEF, mRotationButton));
|
||||
final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
|
||||
doReturn(view).when(mButton).getCurrentView();
|
||||
doReturn(kbd).when(mButton).getNewDrawable();
|
||||
doReturn(view).when(mRotationButton).getCurrentView();
|
||||
doReturn(true).when(mRotationButton).acceptRotationProposal();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInvalidRotationProposal() {
|
||||
mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, false /* isValid */);
|
||||
verify(mButton, times(1)).setRotateSuggestionButtonState(false /* visible */);
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
|
||||
false /* isValid */);
|
||||
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSameRotationProposal() {
|
||||
mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE, true /* isValid */);
|
||||
verify(mButton, times(1)).setRotateSuggestionButtonState(false /* visible */);
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE,
|
||||
true /* isValid */);
|
||||
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRotationProposalShowButtonShowNav() {
|
||||
// No navigation bar should not call to set visibility state
|
||||
mButton.onNavigationBarWindowVisibilityChange(false /* showing */);
|
||||
verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
|
||||
verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
|
||||
mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
|
||||
// No navigation bar with rotation change should not call to set visibility state
|
||||
mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, true /* isValid */);
|
||||
verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
|
||||
verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
|
||||
true /* isValid */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
|
||||
// Since rotation has changed rotation should be pending, show mButton when showing nav bar
|
||||
mButton.onNavigationBarWindowVisibilityChange(true /* showing */);
|
||||
verify(mButton, times(1)).setRotateSuggestionButtonState(true /* visible */);
|
||||
mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
|
||||
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRotationProposalShowButton() {
|
||||
// Navigation bar being visible should not call to set visibility state
|
||||
mButton.onNavigationBarWindowVisibilityChange(true /* showing */);
|
||||
verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
|
||||
verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
|
||||
mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
|
||||
// Navigation bar is visible and rotation requested
|
||||
mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, true /* isValid */);
|
||||
verify(mButton, times(1)).setRotateSuggestionButtonState(true /* visible */);
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
|
||||
true /* isValid */);
|
||||
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user