From 939fad5ca192a54fc555226a2839fadc7aee0ca8 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Mon, 28 Nov 2022 15:24:28 -0800 Subject: [PATCH 1/8] Initialize navigation mode for TaskbarDelegate * Previously we were only initializing nav mode for EdgeBackGestureHandler and never updating state in TaskbarDelegate Fixes: 260226345 Test: Wrote new tests, killed sysui process and dump state in terminal and observe nav mode matches between NavigationModeController and TaskbarDelegate Change-Id: I637fbf759ab86cd9ff25875e24dca9327fee35fa --- .../navigationbar/TaskbarDelegate.java | 11 ++- .../navigationbar/TaskbarDelegateTest.kt | 93 +++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 0d9af31639f2e..3dec5138de956 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -55,6 +55,7 @@ import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.view.AppearanceRegion; @@ -125,7 +126,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final DisplayManager mDisplayManager; private Context mWindowContext; private ScreenPinningNotify mScreenPinningNotify; - private int mNavigationMode; + private int mNavigationMode = -1; private final Consumer mPipListener; /** @@ -217,8 +218,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, parseCurrentSysuiState(); mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(this); - mEdgeBackGestureHandler.onNavigationModeChanged( - mNavigationModeController.addListener(this)); + onNavigationModeChanged(mNavigationModeController.addListener(this)); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mNavBarHelper.init(); mEdgeBackGestureHandler.onNavBarAttached(); @@ -492,6 +492,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, !QuickStepContract.isGesturalMode(mNavigationMode)); } + @VisibleForTesting + int getNavigationMode() { + return mNavigationMode; + } + @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt new file mode 100644 index 0000000000000..1742c6994246f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -0,0 +1,93 @@ +package com.android.systemui.navigationbar + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.model.SysUiState +import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler +import com.android.systemui.recents.OverviewProxyService +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.phone.AutoHideController +import com.android.systemui.statusbar.phone.LightBarController +import com.android.systemui.statusbar.phone.LightBarTransitionsController +import com.android.wm.shell.back.BackAnimation +import com.android.wm.shell.pip.Pip +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.Optional + +@SmallTest +class TaskbarDelegateTest : SysuiTestCase() { + val DISPLAY_ID = 0; + val MODE_GESTURE = 0; + val MODE_THREE_BUTTON = 1; + + private lateinit var mTaskbarDelegate: TaskbarDelegate + @Mock + lateinit var mEdgeBackGestureHandlerFactory : EdgeBackGestureHandler.Factory + @Mock + lateinit var mEdgeBackGestureHandler : EdgeBackGestureHandler + @Mock + lateinit var mLightBarControllerFactory : LightBarTransitionsController.Factory + @Mock + lateinit var mLightBarTransitionController: LightBarTransitionsController + @Mock + lateinit var mCommandQueue: CommandQueue + @Mock + lateinit var mOverviewProxyService: OverviewProxyService + @Mock + lateinit var mNavBarHelper: NavBarHelper + @Mock + lateinit var mNavigationModeController: NavigationModeController + @Mock + lateinit var mSysUiState: SysUiState + @Mock + lateinit var mDumpManager: DumpManager + @Mock + lateinit var mAutoHideController: AutoHideController + @Mock + lateinit var mLightBarController: LightBarController + @Mock + lateinit var mOptionalPip: Optional + @Mock + lateinit var mBackAnimation: BackAnimation + @Mock + lateinit var mCurrentSysUiState: NavBarHelper.CurrentSysuiState + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(mEdgeBackGestureHandlerFactory.create(context)).thenReturn(mEdgeBackGestureHandler) + `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController) + `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState) + `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState) + mTaskbarDelegate = TaskbarDelegate(context, mEdgeBackGestureHandlerFactory, + mLightBarControllerFactory) + mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper, + mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController, + mLightBarController, mOptionalPip, mBackAnimation) + } + + @Test + fun navigationModeInitialized() { + `when`(mNavigationModeController.addListener(any())).thenReturn(MODE_THREE_BUTTON) + assert(mTaskbarDelegate.navigationMode == -1) + mTaskbarDelegate.init(DISPLAY_ID) + assert(mTaskbarDelegate.navigationMode == MODE_THREE_BUTTON) + } + + @Test + fun navigationModeInitialized_notifyEdgeBackHandler() { + `when`(mNavigationModeController.addListener(any())).thenReturn(MODE_GESTURE) + mTaskbarDelegate.init(DISPLAY_ID) + verify(mEdgeBackGestureHandler, times(1)).onNavigationModeChanged(MODE_GESTURE) + } +} \ No newline at end of file From 4e386dfec6118745ada8431ad21d9ae2f71876a8 Mon Sep 17 00:00:00 2001 From: Lyn Han Date: Tue, 6 Dec 2022 14:52:10 -0800 Subject: [PATCH 2/8] Add FSI debug class Bug: 243421660 Test: logging works for local development Change-Id: I54e8bc6542dd3b8b2ea4bffc2bbe63a9534acf35 --- .../statusbar/notification/fsi/FsiDebug.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt new file mode 100644 index 0000000000000..d9e3f8fbf1468 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt @@ -0,0 +1,16 @@ +package com.android.systemui.statusbar.notification.fsi + +class FsiDebug { + + companion object { + private const val debugTag = "FsiDebug" + private const val debug = true + + fun log(s: Any) { + if (!debug) { + return + } + android.util.Log.d(debugTag, "$s") + } + } +} \ No newline at end of file From a6000e21cbcc47aa055cc3782192903fe178933b Mon Sep 17 00:00:00 2001 From: Will Leshner Date: Thu, 8 Dec 2022 14:59:28 -0800 Subject: [PATCH 3/8] Don't allow dreaming for users that can't dream. DreamManagerService#canStartDreaming should not return true if the current user is not allowed to dream. Bug: 261907079 Test: manually by switching to a user that can't dream and making sure that the screen turns off on power press while docked. Change-Id: Ia4a46dff5360d3565762b82fb4353e66f0b8f661 --- .../java/com/android/server/dreams/DreamManagerService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 3a49d8627c23a..d328fd7cacbb5 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -379,6 +379,10 @@ public final class DreamManagerService extends SystemService { return false; } + if (!dreamsEnabledForUser(ActivityManager.getCurrentUser())) { + return false; + } + if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) { return mIsCharging; } From c5f6dc060408b8ce9ce293ec99c83b17d2ac60bf Mon Sep 17 00:00:00 2001 From: Will Leshner Date: Mon, 28 Nov 2022 15:06:03 -0800 Subject: [PATCH 4/8] Lock device on short power button press. If a device is configured to dream on short power press, also make sure it locks as well. Also fixes an issue where the screen does not turn off when another user (not user zero) presses the power button. Bug: 260135090 Test: manually by configuring device to dream on short power press and then making sure it is also locked after press. Change-Id: I16ca4eab7a0e8a3f2ef42c090147bd7e62445c69 --- .../android/server/policy/PhoneWindowManager.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6d5da3254eac1..85a2a5d0e0ebf 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1049,6 +1049,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { return; } + // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing + // the lock screen before the dream appears. Note that this locking behavior needs to + // happen regardless of whether we end up dreaming (below) or not. + // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank. + lockNow(null); + + // Don't dream if the user isn't user zero. + // TODO(b/261907079): Move this check to DreamManagerService#canStartDreamingInternal(). + if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) { + noDreamAction.run(); + return; + } + final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal(); if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) { noDreamAction.run(); From a19956d62f7dab77a82b5dffac54a351c2bcf11c Mon Sep 17 00:00:00 2001 From: Edward Savage-Jones Date: Fri, 21 Oct 2022 13:16:36 +0200 Subject: [PATCH 5/8] Maintain the users current value for STATUS_BAR_SHOW_VIBRATE_ICON Make sure not to overwrite any current user selection for Secure.STATUS_BAR_SHOW_VIBRATE_ICON that the user may have made. Issue: 253992986 Test: Manual - see issue Change-Id: I33155a27707e6d30ff56fb090c71b9670a98fc31 --- .../providers/settings/SettingsProvider.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 19bbcff0bd79d..ded7e785ca756 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -5514,13 +5514,17 @@ public class SettingsProvider extends ContentProvider { } if (currentVersion == 210) { final SettingsState secureSettings = getSecureSettingsLocked(userId); - final int defaultValueVibrateIconEnabled = getContext().getResources() - .getInteger(R.integer.def_statusBarVibrateIconEnabled); - secureSettings.insertSettingOverrideableByRestoreLocked( - Secure.STATUS_BAR_SHOW_VIBRATE_ICON, - String.valueOf(defaultValueVibrateIconEnabled), - null /* tag */, true /* makeDefault */, - SettingsState.SYSTEM_PACKAGE_NAME); + final Setting currentSetting = secureSettings.getSettingLocked( + Secure.STATUS_BAR_SHOW_VIBRATE_ICON); + if (currentSetting.isNull()) { + final int defaultValueVibrateIconEnabled = getContext().getResources() + .getInteger(R.integer.def_statusBarVibrateIconEnabled); + secureSettings.insertSettingOverrideableByRestoreLocked( + Secure.STATUS_BAR_SHOW_VIBRATE_ICON, + String.valueOf(defaultValueVibrateIconEnabled), + null /* tag */, true /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } currentVersion = 211; } // vXXX: Add new settings above this point. From a2b566109f60776abd7dc2e9e2ffed229c2e1f38 Mon Sep 17 00:00:00 2001 From: Vania Januar Date: Mon, 28 Nov 2022 14:07:24 +0000 Subject: [PATCH 6/8] StylusManager for managing local stylus listeners and state. The StylusManager is a SysUISingleton that can notify apps when styluses are added, removed, or changed. Bug: 250908244 Test: StylusManagerTest Change-Id: I98e0aeee6824f6be6b66f85ec5bdf0c21f219b7b --- .../android/systemui/stylus/StylusManager.kt | 131 +++++++++++ .../systemui/stylus/StylusManagerTest.kt | 221 ++++++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt new file mode 100644 index 0000000000000..d9ede44faa2e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 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.stylus + +import android.hardware.input.InputManager +import android.os.Handler +import android.util.ArrayMap +import android.view.InputDevice +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import java.util.concurrent.CopyOnWriteArrayList +import javax.inject.Inject + +/** + * A class which keeps track of InputDevice events related to stylus devices, and notifies + * registered callbacks of stylus events. + */ +@SysUISingleton +class StylusManager +@Inject +constructor( + private val inputManager: InputManager, + @Background private val handler: Handler, +) : InputManager.InputDeviceListener { + + private val stylusCallbacks: CopyOnWriteArrayList = CopyOnWriteArrayList() + // This map should only be accessed on the handler + private val inputDeviceAddressMap: MutableMap = ArrayMap() + + /** + * Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot + * at time of starting. + */ + fun startListener() { + addExistingStylusToMap() + inputManager.registerInputDeviceListener(this, handler) + } + + /** Registers a StylusCallback to listen to stylus events. */ + fun registerCallback(callback: StylusCallback) { + stylusCallbacks.add(callback) + } + + /** Unregisters a StylusCallback. If StylusCallback is not registered, is a no-op. */ + fun unregisterCallback(callback: StylusCallback) { + stylusCallbacks.remove(callback) + } + + override fun onInputDeviceAdded(deviceId: Int) { + val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return + if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return + + // TODO(b/257936830): get address once input api available + val btAddress: String? = null + inputDeviceAddressMap[deviceId] = btAddress + executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) } + + if (btAddress != null) { + executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) } + } + } + + override fun onInputDeviceChanged(deviceId: Int) { + val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return + if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return + + // TODO(b/257936830): get address once input api available + val currAddress: String? = null + val prevAddress: String? = inputDeviceAddressMap[deviceId] + inputDeviceAddressMap[deviceId] = currAddress + + if (prevAddress == null && currAddress != null) { + executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) } + } + + if (prevAddress != null && currAddress == null) { + executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) } + } + } + + override fun onInputDeviceRemoved(deviceId: Int) { + if (!inputDeviceAddressMap.contains(deviceId)) return + + val btAddress: String? = inputDeviceAddressMap[deviceId] + inputDeviceAddressMap.remove(deviceId) + if (btAddress != null) { + executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) } + } + executeStylusCallbacks { cb -> cb.onStylusRemoved(deviceId) } + } + + private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) { + stylusCallbacks.forEach(run) + } + + private fun addExistingStylusToMap() { + for (deviceId: Int in inputManager.inputDeviceIds) { + val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue + if (device.supportsSource(InputDevice.SOURCE_STYLUS)) { + // TODO(b/257936830): get address once input api available + inputDeviceAddressMap[deviceId] = null + } + } + } + + /** Callback interface to receive events from the StylusManager. */ + interface StylusCallback { + fun onStylusAdded(deviceId: Int) {} + fun onStylusRemoved(deviceId: Int) {} + fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {} + fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {} + } + + companion object { + private val TAG = StylusManager::class.simpleName.orEmpty() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt new file mode 100644 index 0000000000000..e6ec20e0ea734 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2022 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.stylus + +import android.hardware.input.InputManager +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.view.InputDevice +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@Ignore("b/257936830 until bt APIs") +class StylusManagerTest : SysuiTestCase() { + @Mock lateinit var inputManager: InputManager + + @Mock lateinit var stylusDevice: InputDevice + + @Mock lateinit var btStylusDevice: InputDevice + + @Mock lateinit var otherDevice: InputDevice + + @Mock lateinit var handler: Handler + + @Mock lateinit var stylusCallback: StylusManager.StylusCallback + + @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback + + private lateinit var stylusManager: StylusManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(handler.post(any())).thenAnswer { + (it.arguments[0] as Runnable).run() + true + } + + stylusManager = StylusManager(inputManager, handler) + + stylusManager.registerCallback(stylusCallback) + + whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false) + whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) + whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) + + // whenever(stylusDevice.bluetoothAddress).thenReturn(null) + // whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS) + + whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice) + whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice) + whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice) + whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID)) + } + + @Test + fun startListener_registersInputDeviceListener() { + stylusManager.startListener() + + verify(inputManager, times(1)).registerInputDeviceListener(stylusManager, handler) + } + + @Test + fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() { + stylusManager.registerCallback(otherStylusCallback) + + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID) + verifyNoMoreInteractions(stylusCallback) + verify(otherStylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID) + verifyNoMoreInteractions(otherStylusCallback) + } + + @Test + fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID) + verifyNoMoreInteractions(stylusCallback) + } + + @Test + fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + inOrder(stylusCallback).let { + it.verify(stylusCallback, times(1)).onStylusAdded(BT_STYLUS_DEVICE_ID) + it.verify(stylusCallback, times(1)) + .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + } + } + + @Test + fun onInputDeviceAdded_notStylus_doesNotCallCallbacks() { + stylusManager.onInputDeviceAdded(OTHER_DEVICE_ID) + + verifyNoMoreInteractions(stylusCallback) + } + + @Test + fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS) + stylusManager.registerCallback(otherStylusCallback) + + stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)) + .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + verify(otherStylusCallback, times(1)) + .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + } + + @Test + fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS) + + stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)) + .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + } + + @Test + fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + // whenever(btStylusDevice.bluetoothAddress).thenReturn(null) + + stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)) + .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + } + + @Test + fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)) + .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + } + + @Test + fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID) + + verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any()) + } + + @Test + fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + stylusManager.registerCallback(otherStylusCallback) + + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID) + verify(otherStylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID) + } + + @Test + fun onInputDeviceRemoved_stylus_callsCallbacks() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID) + verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any()) + } + + @Test + fun onInputDeviceRemoved_btStylus_callsCallbacks() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID) + + inOrder(stylusCallback).let { + it.verify(stylusCallback, times(1)) + .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS) + it.verify(stylusCallback, times(1)).onStylusRemoved(BT_STYLUS_DEVICE_ID) + } + } + + companion object { + private const val OTHER_DEVICE_ID = 0 + private const val STYLUS_DEVICE_ID = 1 + private const val BT_STYLUS_DEVICE_ID = 2 + + private const val STYLUS_BT_ADDRESS = "SOME:ADDRESS" + } +} From 79691170113776f0801c407ecd232da976052a36 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Fri, 9 Dec 2022 14:40:09 -0500 Subject: [PATCH 7/8] Exempt ongoing and media notifications from unseen filter Test: atest KeygaurdCoordinatorTet Bug: 240472040 Fixes: 260563613 Change-Id: Iec08a548fd789161effbb1ac89a096f3ae008299 --- .../coordinator/KeyguardCoordinator.kt | 10 ++++ .../coordinator/KeyguardCoordinatorTest.kt | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 9da94ce968c7e..4133802189a51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -119,6 +119,9 @@ constructor( // Don't apply the filter to (non-promoted) group summaries // - summary will be pruned if necessary, depending on if children are filtered entry.parent?.summary == entry -> false + // Check that the entry satisfies certain characteristics that would bypass the + // filter + shouldIgnoreUnseenCheck(entry) -> false else -> true }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered } @@ -134,6 +137,13 @@ constructor( keyguardNotificationVisibilityProvider.shouldHideNotification(entry) } + private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean = + when { + entry.isMediaNotification -> true + entry.sbn.isOngoing -> true + else -> false + } + // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on // these same updates private fun setupInvalidateNotifListCallbacks() {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 7f73856bff896..5f19fac8666a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator +import android.app.Notification import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -33,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.provider.SectionHe import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor @@ -104,6 +106,50 @@ class KeyguardCoordinatorTest : SysuiTestCase() { } } + @Test + fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() { + whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true) + + // GIVEN: Keyguard is not showing, and an ongoing notification is present + keyguardRepository.setKeyguardShowing(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder() + .setNotification(Notification.Builder(mContext).setOngoing(true).build()) + .build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "ongoing" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() { + whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true) + + // GIVEN: Keyguard is not showing, and a media notification is present + keyguardRepository.setKeyguardShowing(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build().apply { + row = mock().apply { + whenever(isMediaRow).thenReturn(true) + } + } + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "media" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + @Test fun unseenFilterUpdatesSeenProviderWhenSuppressing() { whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true) From be0808498c3fc09ecf52eaddcb3dae88c2aa562a Mon Sep 17 00:00:00 2001 From: Shan Huang Date: Thu, 1 Dec 2022 23:50:43 +0000 Subject: [PATCH 8/8] Fix Dialog default callback registering at the wrong priority. Bug:259602785 Test: In a standalone app, register a callback in Dialog#onCreate() with default priority. Make sure the dialog callback can be invoked. Test: BackNavigationTests#registerCallback_dialog (run on master) Change-Id: I9cfd596611fb341389cda44e3605c1e99ddc3791 --- core/java/android/app/Dialog.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index de0f7522f36d6..411d157fa9278 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -458,8 +458,7 @@ public class Dialog implements DialogInterface, Window.Callback, && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { // Add onBackPressed as default back behavior. mDefaultBackCallback = this::onBackPressed; - getOnBackInvokedDispatcher().registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mDefaultBackCallback); + getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); mDefaultBackCallback = null; } }