diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java new file mode 100644 index 0000000000000..ebfea450af884 --- /dev/null +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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.internal.policy; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +/** + * @hide + */ +public class GestureNavigationSettingsObserver extends ContentObserver { + private Context mContext; + private Runnable mOnChangeRunnable; + + public GestureNavigationSettingsObserver(Handler handler, Context context, + Runnable onChangeRunnable) { + super(handler); + mContext = context; + mOnChangeRunnable = onChangeRunnable; + } + + public void register() { + ContentResolver r = mContext.getContentResolver(); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT), + false, this, UserHandle.USER_ALL); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT), + false, this, UserHandle.USER_ALL); + } + + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + if (mOnChangeRunnable != null) { + mOnChangeRunnable.run(); + } + } + + public int getLeftSensitivity(Resources userRes) { + return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT); + } + + public int getRightSensitivity(Resources userRes) { + return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT); + } + + private int getSensitivity(Resources userRes, String side) { + final int inset = userRes.getDimensionPixelSize( + com.android.internal.R.dimen.config_backGestureInset); + final float scale = Settings.Secure.getFloatForUser( + mContext.getContentResolver(), side, 1.0f, UserHandle.USER_CURRENT); + return (int) (inset * scale); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 745843deeddb1..adca10ff7677f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -44,6 +44,7 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; @@ -99,8 +100,10 @@ public class EdgeBackGestureHandler implements DisplayListener, private final Region mExcludeRegion = new Region(); private final Region mUnrestrictedExcludeRegion = new Region(); - // The edge width where touch down is allowed - private int mEdgeWidth; + // The left side edge width where touch down is allowed + private int mEdgeWidthLeft; + // The right side edge width where touch down is allowed + private int mEdgeWidthRight; // The bottom gesture area height private int mBottomGestureHeight; // The slop to distinguish between horizontal and vertical motion @@ -127,6 +130,8 @@ public class EdgeBackGestureHandler implements DisplayListener, private int mRightInset; private int mSysUiFlags; + private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; + private final NavigationEdgeBackPlugin.BackCallback mBackCallback = new NavigationEdgeBackPlugin.BackCallback() { @Override @@ -174,13 +179,17 @@ public class EdgeBackGestureHandler implements DisplayListener, mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, ViewConfiguration.getLongPressTimeout()); + mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( + mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res)); + updateCurrentUserResources(res); sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); } public void updateCurrentUserResources(Resources res) { - mEdgeWidth = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_backGestureInset); + mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); + mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); + mBottomGestureHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_gesture_height); } @@ -236,6 +245,7 @@ public class EdgeBackGestureHandler implements DisplayListener, } if (!mIsEnabled) { + mGestureNavigationSettingsObserver.unregister(); mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); mPluginManager.removePluginListener(this); @@ -248,6 +258,7 @@ public class EdgeBackGestureHandler implements DisplayListener, } } else { + mGestureNavigationSettingsObserver.register(); updateDisplaySize(); mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, mContext.getMainThreadHandler()); @@ -321,7 +332,8 @@ public class EdgeBackGestureHandler implements DisplayListener, private boolean isWithinTouchRegion(int x, int y) { // Disallow if too far from the edge - if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) { + if (x > mEdgeWidthLeft + mLeftInset + && x < (mDisplaySize.x - mEdgeWidthRight - mRightInset)) { return false; } @@ -364,7 +376,7 @@ public class EdgeBackGestureHandler implements DisplayListener, if (action == MotionEvent.ACTION_DOWN) { // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden - mIsOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset; + mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; mInRejectedExclusion = false; mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags) && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); @@ -461,7 +473,8 @@ public class EdgeBackGestureHandler implements DisplayListener, pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); pw.println(" mIsAttached=" + mIsAttached); - pw.println(" mEdgeWidth=" + mEdgeWidth); + pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); + pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java index 43ac4cf732aa9..d24ccf343a3ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.om.IOverlayManager; +import android.content.om.OverlayInfo; import android.content.pm.PackageManager; import android.content.res.ApkAssets; import android.os.PatternMatcher; @@ -172,6 +173,9 @@ public class NavigationModeController implements Dumpable { public void updateCurrentInteractionMode(boolean notify) { mCurrentUserContext = getCurrentUserContext(); int mode = getCurrentInteractionMode(mCurrentUserContext); + if (mode == NAV_BAR_MODE_GESTURAL) { + switchToDefaultGestureNavOverlayIfNecessary(); + } mUiBgExecutor.execute(() -> { Settings.Secure.putString(mCurrentUserContext.getContentResolver(), Secure.NAVIGATION_MODE, String.valueOf(mode)); @@ -277,6 +281,34 @@ public class NavigationModeController implements Dumpable { } } + private void switchToDefaultGestureNavOverlayIfNecessary() { + final int userId = mCurrentUserContext.getUserId(); + try { + final IOverlayManager om = mOverlayManager; + final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId); + if (info != null && !info.isEnabled()) { + // Enable the default gesture nav overlay, and move the back gesture inset scale to + // Settings.Secure for left and right sensitivity. + final int curInset = mCurrentUserContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_backGestureInset); + om.setEnabledExclusiveInCategory(NAV_BAR_MODE_GESTURAL_OVERLAY, userId); + final int defInset = mCurrentUserContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_backGestureInset); + + final float scale = defInset == 0 ? 1.0f : ((float) curInset) / defInset; + Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(), + Secure.BACK_GESTURE_INSET_SCALE_LEFT, scale); + Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(), + Secure.BACK_GESTURE_INSET_SCALE_RIGHT, scale); + if (DEBUG) { + Log.v(TAG, "Moved back sensitivity for user " + userId + " to scale " + scale); + } + } + } catch (SecurityException | IllegalStateException | RemoteException e) { + Log.e(TAG, "Failed to switch to default gesture nav overlay for user " + userId); + } + } + public void setModeOverlay(String overlayPkg, int userId) { mUiBgExecutor.execute(() -> { try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java deleted file mode 100644 index 5d8044f37c38d..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2008 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 android.view.Display.DEFAULT_DISPLAY; - -import android.annotation.IntDef; -import android.content.ComponentCallbacks; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.Point; -import android.net.Uri; -import android.os.Handler; -import android.provider.Settings; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Coordinates with the prototype settings plugin app that uses Settings.Global to allow different - * prototypes to run in the system. The class will handle communication changes from the settings - * app and call back to listeners. - */ -public class NavigationPrototypeController extends ContentObserver implements ComponentCallbacks { - private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback"; - private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome"; - private static final String PROTOTYPE_ENABLED = "prototype_enabled"; - - public static final String EDGE_SENSITIVITY_WIDTH_SETTING = - "quickstepcontroller_edge_width_sensitivity"; - private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; - public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; - public static final String SHOW_HOME_HANDLE_SETTING = "quickstepcontroller_showhandle"; - public static final String ENABLE_ASSISTANT_GESTURE = "ENABLE_ASSISTANT_GESTURE"; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK, - ACTION_QUICKSWITCH, ACTION_NOTHING, ACTION_ASSISTANT}) - @interface GestureAction {} - static final int ACTION_DEFAULT = 0; - static final int ACTION_QUICKSTEP = 1; - static final int ACTION_QUICKSCRUB = 2; - static final int ACTION_BACK = 3; - static final int ACTION_QUICKSWITCH = 4; - static final int ACTION_NOTHING = 5; - static final int ACTION_ASSISTANT = 6; - - private OnPrototypeChangedListener mListener; - - /** - * Each index corresponds to a different action set in QuickStepController - * {@see updateSwipeLTRBackSetting} - */ - private int[] mActionMap = new int[6]; - - private final Context mContext; - - public NavigationPrototypeController(Context context) { - super(new Handler()); - mContext = context; - updateSwipeLTRBackSetting(); - } - - public void setOnPrototypeChangedListener(OnPrototypeChangedListener listener) { - mListener = listener; - } - - /** - * Observe all the settings to react to from prototype settings - */ - public void register() { - registerObserver(HIDE_BACK_BUTTON_SETTING); - registerObserver(HIDE_HOME_BUTTON_SETTING); - registerObserver(GESTURE_MATCH_SETTING); - registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING); - registerObserver(SHOW_HOME_HANDLE_SETTING); - registerObserver(ENABLE_ASSISTANT_GESTURE); - mContext.registerComponentCallbacks(this); - } - - /** - * Disable observing settings to react to from prototype settings - */ - public void unregister() { - mContext.getContentResolver().unregisterContentObserver(this); - mContext.unregisterComponentCallbacks(this); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - if (!selfChange && mListener != null) { - final String path = uri.getPath(); - if (path.endsWith(GESTURE_MATCH_SETTING)) { - // Get the settings gesture map corresponding to each action - // {@see updateSwipeLTRBackSetting} - updateSwipeLTRBackSetting(); - mListener.onGestureRemap(mActionMap); - } else if (path.endsWith(HIDE_BACK_BUTTON_SETTING)) { - mListener.onBackButtonVisibilityChanged( - !getGlobalBool(HIDE_BACK_BUTTON_SETTING, false)); - } else if (path.endsWith(HIDE_HOME_BUTTON_SETTING)) { - mListener.onHomeButtonVisibilityChanged(!hideHomeButton()); - } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) { - mListener.onColorAdaptChanged(mContext.getDisplayId() == DEFAULT_DISPLAY); - } else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) { - mListener.onHomeHandleVisiblilityChanged(showHomeHandle()); - } else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) { - mListener.onAssistantGestureEnabled(isAssistantGestureEnabled()); - } - } - } - - /** - * @return the width for edge swipe - */ - public int getEdgeSensitivityWidth() { - return mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.config_backGestureInset); - } - - /** - * @return full screen height - */ - public int getEdgeSensitivityHeight() { - final Point size = new Point(); - mContext.getDisplay().getRealSize(size); - return size.y; - } - - public boolean isEnabled() { - return getGlobalBool(PROTOTYPE_ENABLED, false); - } - - /** - * Retrieve the action map to apply to the quick step controller - * @return an action map - */ - int[] getGestureActionMap() { - return mActionMap; - } - - /** - * @return if home button should be invisible - */ - boolean hideHomeButton() { - return getGlobalBool(HIDE_HOME_BUTTON_SETTING, false /* default */); - } - - boolean showHomeHandle() { - return getGlobalBool(SHOW_HOME_HANDLE_SETTING, false /* default */); - } - - boolean isAssistantGestureEnabled() { - return getGlobalBool(ENABLE_ASSISTANT_GESTURE, false /* default */); - } - - - /** - * Since Settings.Global cannot pass arrays, use a string to represent each character as a - * gesture map to actions corresponding to {@see GestureAction}. The number is represented as: - * Number: [up] [down] [left] [right] - */ - private void updateSwipeLTRBackSetting() { - String value = Settings.Global.getString(mContext.getContentResolver(), - GESTURE_MATCH_SETTING); - if (value != null) { - for (int i = 0; i < mActionMap.length; ++i) { - mActionMap[i] = Character.getNumericValue(value.charAt(i)); - } - } - } - - private boolean getGlobalBool(String name, boolean defaultVal) { - return Settings.Global.getInt(mContext.getContentResolver(), name, defaultVal ? 1 : 0) == 1; - } - - private int getGlobalInt(String name, int defaultVal) { - return Settings.Global.getInt(mContext.getContentResolver(), name, defaultVal); - } - - private void registerObserver(String name) { - mContext.getContentResolver() - .registerContentObserver(Settings.Global.getUriFor(name), false, this); - } - - private static int convertDpToPixel(float dp) { - return (int) (dp * Resources.getSystem().getDisplayMetrics().density); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - if (mListener != null) { - mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(), - getEdgeSensitivityHeight()); - } - } - - @Override - public void onLowMemory() { - } - - public interface OnPrototypeChangedListener { - void onGestureRemap(@GestureAction int[] actions); - void onBackButtonVisibilityChanged(boolean visible); - void onHomeButtonVisibilityChanged(boolean visible); - void onHomeHandleVisiblilityChanged(boolean visible); - void onColorAdaptChanged(boolean enabled); - void onEdgeSensitivityChanged(int width, int height); - void onAssistantGestureEnabled(boolean enabled); - } -} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 98e3d07fc2e48..94821ace2a051 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6523,6 +6523,7 @@ class DisplayContent extends WindowContainer { + synchronized (mLock) { + onConfigurationChanged(); + mSystemGestures.onConfigurationChanged(); + mDisplayContent.updateSystemGestureExclusion(); + } + }); + mHandler.post(mGestureNavigationSettingsObserver::register); } void systemReady() { @@ -723,7 +738,7 @@ public class DisplayPolicy { } boolean hasSideGestures() { - return mHasNavigationBar && mSideGestureInset > 0; + return mHasNavigationBar && (mLeftGestureInset > 0 || mRightGestureInset > 0); } public boolean navigationBarCanMove() { @@ -1076,11 +1091,12 @@ public class DisplayPolicy { inOutFrame.left = 0; inOutFrame.top = 0; inOutFrame.bottom = displayFrames.mDisplayHeight; - inOutFrame.right = displayFrames.mUnrestricted.left + mSideGestureInset; + inOutFrame.right = displayFrames.mUnrestricted.left + mLeftGestureInset; }); mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win, (displayFrames, windowState, inOutFrame) -> { - inOutFrame.left = displayFrames.mUnrestricted.right - mSideGestureInset; + inOutFrame.left = displayFrames.mUnrestricted.right + - mRightGestureInset; inOutFrame.top = 0; inOutFrame.bottom = displayFrames.mDisplayHeight; inOutFrame.right = displayFrames.mDisplayWidth; @@ -2819,7 +2835,8 @@ public class DisplayPolicy { } mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode); - mSideGestureInset = res.getDimensionPixelSize(R.dimen.config_backGestureInset); + mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res); + mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res); mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough); mNavigationBarAlwaysShowOnSideGesture = res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture); @@ -3953,6 +3970,10 @@ public class DisplayPolicy { return false; } + void release() { + mHandler.post(mGestureNavigationSettingsObserver::unregister); + } + @VisibleForTesting static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) { if (navBarWindow == null || !navBarWindow.isVisibleLw() diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 560d03fe7d9ef..091f493b4687c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -322,6 +322,8 @@ public class SystemServicesTestRule implements TestRule { } private void tearDown() { + mWmService.mRoot.forAllDisplayPolicies(DisplayPolicy::release); + // Unregister display listener from root to avoid issues with subsequent tests. mContext.getSystemService(DisplayManager.class) .unregisterDisplayListener(mAtmService.mRootWindowContainer);