diff options
103 files changed, 2925 insertions, 1039 deletions
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index a4a38c713a66..86c8097ee674 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3871,12 +3871,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskDisplayArea.java" }, - "1648338379": { - "message": "Display id=%d is ignoring all orientation requests, return %d", - "level": "VERBOSE", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/DisplayContent.java" - }, "1653025361": { "message": "Register task fragment organizer=%s uid=%d pid=%d", "level": "VERBOSE", @@ -4117,6 +4111,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java" }, + "1877863087": { + "message": "Display id=%d is ignoring orientation request for %d, return %d", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "1878927091": { "message": "prepareSurface: No changes in animation for %s", "level": "VERBOSE", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index b70b320eee3c..9b16877e39e7 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -35,7 +35,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.window.WindowProvider; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -129,9 +128,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { }); mWindowLayoutChangeListeners.put(context, consumer); - // TODO(b/258065175) Further extend this to ContextWrappers. - if (context instanceof WindowProvider) { - final IBinder windowContextToken = context.getWindowContextToken(); + final IBinder windowContextToken = context.getWindowContextToken(); + if (windowContextToken != null) { + // We register component callbacks for window contexts. For activity contexts, they will + // receive callbacks from NotifyOnConfigurationChanged instead. final ConfigurationChangeListener listener = new ConfigurationChangeListener(windowContextToken); context.registerComponentCallbacks(listener); @@ -150,8 +150,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { continue; } - if (context instanceof WindowProvider) { - final IBinder token = context.getWindowContextToken(); + final IBinder token = context.getWindowContextToken(); + if (token != null) { context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token)); mConfigurationChangeListeners.remove(token); } @@ -308,9 +308,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return false; } final int windowingMode; - if (context instanceof Activity) { + IBinder activityToken = context.getActivityToken(); + if (activityToken != null) { final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration( - context.getActivityToken()); + activityToken); if (taskConfig == null) { // If we cannot determine the task configuration for any reason, it is likely that // we won't be able to determine its position correctly as well. DisplayFeatures' diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 1a8b9540cbd0..2ac1dc0c4838 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -69,6 +69,9 @@ android_test { enabled: false, }, + platform_apis: true, + certificate: "platform", + aaptflags: [ "--extra-packages", "com.android.wm.shell.tests", diff --git a/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml b/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml index 5b52a04e1cf6..6afeaf81551d 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_le_audio.xml @@ -13,6 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> + +<!-- The LE audio icon is like headphones and it is the same as ic_bt_headset_hfp.xml--> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" @@ -20,12 +22,8 @@ android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal" > <path - android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z" - android:fillColor="#FFFFFFFF"/> - <path - android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" - android:fillColor="#FFFFFFFF"/> - <path - android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z" - android:fillColor="#FFFFFFFF"/> + android:fillColor="#FF000000" + android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 + 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 + -9,-9z"/> </vector> diff --git a/packages/SettingsLib/res/drawable/ic_bt_le_audio_speakers.xml b/packages/SettingsLib/res/drawable/ic_bt_le_audio_speakers.xml new file mode 100644 index 000000000000..ac3053a556b2 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_bt_le_audio_speakers.xml @@ -0,0 +1,31 @@ +<!-- + Copyright 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal" > + <path + android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z" + android:fillColor="#FFFFFFFF"/> + <path + android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" + android:fillColor="#FFFFFFFF"/> + <path + android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z" + android:fillColor="#FFFFFFFF"/> +</vector> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index a9f4e9c74103..4d6dd4b538cc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -233,7 +233,22 @@ public class BluetoothEventManager { @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) { + Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); boolean isActive = Objects.equals(cachedDevice, activeDevice); + if (!isActive && !memberSet.isEmpty()) { + for (CachedBluetoothDevice memberCachedDevice : memberSet) { + isActive = Objects.equals(memberCachedDevice, activeDevice); + if (isActive) { + Log.d(TAG, + "The active device is the member device " + + activeDevice.getDevice().getAnonymizedAddress() + + ". change activeDevice as main device " + + cachedDevice.getDevice().getAnonymizedAddress()); + activeDevice = cachedDevice; + break; + } + } + } cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile); } for (BluetoothCallback callback : mCallbacks) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index 7b94492cef4f..3361a66e958d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -70,10 +70,14 @@ public class BluetoothEventManagerTest { @Mock private HearingAidProfile mHearingAidProfile; @Mock + private LeAudioProfile mLeAudioProfile; + @Mock private BluetoothDevice mDevice1; @Mock private BluetoothDevice mDevice2; @Mock + private BluetoothDevice mDevice3; + @Mock private LocalBluetoothProfileManager mLocalProfileManager; @Mock private BluetoothUtils.ErrorListener mErrorListener; @@ -83,6 +87,7 @@ public class BluetoothEventManagerTest { private BluetoothEventManager mBluetoothEventManager; private CachedBluetoothDevice mCachedDevice1; private CachedBluetoothDevice mCachedDevice2; + private CachedBluetoothDevice mCachedDevice3; @Before public void setUp() { @@ -95,9 +100,10 @@ public class BluetoothEventManagerTest { when(mHfpProfile.isProfileReady()).thenReturn(true); when(mA2dpProfile.isProfileReady()).thenReturn(true); when(mHearingAidProfile.isProfileReady()).thenReturn(true); - + when(mLeAudioProfile.isProfileReady()).thenReturn(true); mCachedDevice1 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1); mCachedDevice2 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2); + mCachedDevice3 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3); BluetoothUtils.setErrorListener(mErrorListener); } @@ -293,6 +299,43 @@ public class BluetoothEventManagerTest { assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse(); } + @Test + public void dispatchActiveDeviceChanged_connectedMemberDevices_activeDeviceChanged() { + final List<CachedBluetoothDevice> cachedDevices = new ArrayList<>(); + cachedDevices.add(mCachedDevice1); + cachedDevices.add(mCachedDevice2); + + int group1 = 1; + when(mDevice3.getAddress()).thenReturn("testAddress3"); + mCachedDevice1.setGroupId(group1); + mCachedDevice3.setGroupId(group1); + mCachedDevice1.addMemberDevice(mCachedDevice3); + + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); + + // Connect device1 and device3 for LE and device2 for A2DP and HFP + mCachedDevice1.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice3.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice2.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice2.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED); + + // Verify that both devices are connected and none is Active + assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).isFalse(); + assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse(); + assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse(); + assertThat(mCachedDevice3.isActiveDevice(BluetoothProfile.LE_AUDIO)).isFalse(); + + // The member device is active. + mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice3, + BluetoothProfile.LE_AUDIO); + + // The main device is active since the member is active. + assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).isTrue(); + } + /** * Test to verify onActiveDeviceChanged() with A2DP and Hearing Aid. */ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4f08a30ed630..2f5b42ffe641 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -886,7 +886,7 @@ android:showForAllUsers="true" android:finishOnTaskLaunch="true" android:launchMode="singleInstance" - android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt index cb1a5f958ed6..ec5e70383eb3 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt @@ -58,7 +58,9 @@ class FakeKeyguardQuickAffordanceProviderClient( flags: List<KeyguardQuickAffordanceProviderClient.Flag> = listOf( KeyguardQuickAffordanceProviderClient.Flag( - name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED, + name = + KeyguardQuickAffordanceProviderContract.FlagsTable + .FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, value = true, ) ), diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt index 17be74b08690..923b99f0b750 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt @@ -148,7 +148,11 @@ object KeyguardQuickAffordanceProviderContract { /** * Flag denoting whether the customizable lock screen quick affordances feature is enabled. */ - const val FLAG_NAME_FEATURE_ENABLED = "is_feature_enabled" + const val FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED = + "is_custom_lock_screen_quick_affordances_feature_enabled" + + /** Flag denoting whether the customizable clocks feature is enabled. */ + const val FLAG_NAME_CUSTOM_CLOCKS_ENABLED = "is_custom_clocks_feature_enabled" object Columns { /** String. Unique ID for the flag. */ diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index fd2e3249d59a..c5ffc94d01af 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -30,6 +30,9 @@ <bool name="flag_charging_ripple">false</bool> + <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. --> + <bool name="flag_active_unlock_chipbar">true</bool> + <bool name="flag_smartspace">false</bool> <!-- Whether the user switcher chip shows in the status bar. When true, the multi user diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt new file mode 100644 index 000000000000..3a89c13ddd64 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt @@ -0,0 +1,114 @@ +/* + * 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.keyguard + +import android.annotation.CurrentTimeMillisLong +import com.android.systemui.dump.DumpsysTableLogger +import com.android.systemui.dump.Row +import com.android.systemui.plugins.util.RingBuffer + +/** Verbose debug information. */ +data class KeyguardActiveUnlockModel( + @CurrentTimeMillisLong override var timeMillis: Long = 0L, + override var userId: Int = 0, + override var listening: Boolean = false, + // keep sorted + var awakeKeyguard: Boolean = false, + var authInterruptActive: Boolean = false, + var fpLockedOut: Boolean = false, + var primaryAuthRequired: Boolean = false, + var switchingUser: Boolean = false, + var triggerActiveUnlockForAssistant: Boolean = false, + var userCanDismissLockScreen: Boolean = false, +) : KeyguardListenModel() { + + /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ + val asStringList: List<String> by lazy { + listOf( + DATE_FORMAT.format(timeMillis), + timeMillis.toString(), + userId.toString(), + listening.toString(), + // keep sorted + awakeKeyguard.toString(), + authInterruptActive.toString(), + fpLockedOut.toString(), + primaryAuthRequired.toString(), + switchingUser.toString(), + triggerActiveUnlockForAssistant.toString(), + userCanDismissLockScreen.toString(), + ) + } + + /** + * [RingBuffer] to store [KeyguardActiveUnlockModel]. After the buffer is full, it will recycle + * old events. + * + * Do not use [append] to add new elements. Instead use [insert], as it will recycle if + * necessary. + */ + class Buffer { + private val buffer = RingBuffer(CAPACITY) { KeyguardActiveUnlockModel() } + + fun insert(model: KeyguardActiveUnlockModel) { + buffer.advance().apply { + timeMillis = model.timeMillis + userId = model.userId + listening = model.listening + // keep sorted + awakeKeyguard = model.awakeKeyguard + authInterruptActive = model.authInterruptActive + fpLockedOut = model.fpLockedOut + primaryAuthRequired = model.primaryAuthRequired + switchingUser = model.switchingUser + triggerActiveUnlockForAssistant = model.triggerActiveUnlockForAssistant + userCanDismissLockScreen = model.userCanDismissLockScreen + } + } + + /** + * Returns the content of the buffer (sorted from latest to newest). + * + * @see KeyguardFingerprintListenModel.asStringList + */ + fun toList(): List<Row> { + return buffer.asSequence().map { it.asStringList }.toList() + } + } + + companion object { + const val CAPACITY = 20 // number of logs to retain + + /** Headers for dumping a table using [DumpsysTableLogger]. */ + @JvmField + val TABLE_HEADERS = + listOf( + "timestamp", + "time_millis", + "userId", + "listening", + // keep sorted + "awakeKeyguard", + "authInterruptActive", + "fpLockedOut", + "primaryAuthRequired", + "switchingUser", + "triggerActiveUnlockForAssistant", + "userCanDismissLockScreen", + ) + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt new file mode 100644 index 000000000000..deead1959b8a --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -0,0 +1,162 @@ +/* + * 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.keyguard + +import android.annotation.CurrentTimeMillisLong +import com.android.systemui.dump.DumpsysTableLogger +import com.android.systemui.dump.Row +import com.android.systemui.plugins.util.RingBuffer + +/** Verbose debug information associated. */ +data class KeyguardFaceListenModel( + @CurrentTimeMillisLong override var timeMillis: Long = 0L, + override var userId: Int = 0, + override var listening: Boolean = false, + // keep sorted + var authInterruptActive: Boolean = false, + var biometricSettingEnabledForUser: Boolean = false, + var bouncerFullyShown: Boolean = false, + var faceAndFpNotAuthenticated: Boolean = false, + var faceAuthAllowed: Boolean = false, + var faceDisabled: Boolean = false, + var faceLockedOut: Boolean = false, + var goingToSleep: Boolean = false, + var keyguardAwake: Boolean = false, + var keyguardGoingAway: Boolean = false, + var listeningForFaceAssistant: Boolean = false, + var occludingAppRequestingFaceAuth: Boolean = false, + var primaryUser: Boolean = false, + var secureCameraLaunched: Boolean = false, + var supportsDetect: Boolean = false, + var switchingUser: Boolean = false, + var udfpsBouncerShowing: Boolean = false, + var udfpsFingerDown: Boolean = false, + var userNotTrustedOrDetectionIsNeeded: Boolean = false, +) : KeyguardListenModel() { + + /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ + val asStringList: List<String> by lazy { + listOf( + DATE_FORMAT.format(timeMillis), + timeMillis.toString(), + userId.toString(), + listening.toString(), + // keep sorted + authInterruptActive.toString(), + biometricSettingEnabledForUser.toString(), + bouncerFullyShown.toString(), + faceAndFpNotAuthenticated.toString(), + faceAuthAllowed.toString(), + faceDisabled.toString(), + faceLockedOut.toString(), + goingToSleep.toString(), + keyguardAwake.toString(), + keyguardGoingAway.toString(), + listeningForFaceAssistant.toString(), + occludingAppRequestingFaceAuth.toString(), + primaryUser.toString(), + secureCameraLaunched.toString(), + supportsDetect.toString(), + switchingUser.toString(), + udfpsBouncerShowing.toString(), + udfpsFingerDown.toString(), + userNotTrustedOrDetectionIsNeeded.toString(), + ) + } + + /** + * [RingBuffer] to store [KeyguardFaceListenModel]. After the buffer is full, it will recycle + * old events. + * + * Do not use [append] to add new elements. Instead use [insert], as it will recycle if + * necessary. + */ + class Buffer { + private val buffer = RingBuffer(CAPACITY) { KeyguardFaceListenModel() } + + fun insert(model: KeyguardFaceListenModel) { + buffer.advance().apply { + timeMillis = model.timeMillis + userId = model.userId + listening = model.listening + // keep sorted + biometricSettingEnabledForUser = model.biometricSettingEnabledForUser + bouncerFullyShown = model.bouncerFullyShown + faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated + faceAuthAllowed = model.faceAuthAllowed + faceDisabled = model.faceDisabled + faceLockedOut = model.faceLockedOut + goingToSleep = model.goingToSleep + keyguardAwake = model.keyguardAwake + goingToSleep = model.goingToSleep + keyguardGoingAway = model.keyguardGoingAway + listeningForFaceAssistant = model.listeningForFaceAssistant + occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth + primaryUser = model.primaryUser + secureCameraLaunched = model.secureCameraLaunched + supportsDetect = model.supportsDetect + switchingUser = model.switchingUser + udfpsBouncerShowing = model.udfpsBouncerShowing + switchingUser = model.switchingUser + udfpsFingerDown = model.udfpsFingerDown + userNotTrustedOrDetectionIsNeeded = model.userNotTrustedOrDetectionIsNeeded + } + } + /** + * Returns the content of the buffer (sorted from latest to newest). + * + * @see KeyguardFingerprintListenModel.asStringList + */ + fun toList(): List<Row> { + return buffer.asSequence().map { it.asStringList }.toList() + } + } + + companion object { + const val CAPACITY = 40 // number of logs to retain + + /** Headers for dumping a table using [DumpsysTableLogger]. */ + @JvmField + val TABLE_HEADERS = + listOf( + "timestamp", + "time_millis", + "userId", + "listening", + // keep sorted + "authInterruptActive", + "biometricSettingEnabledForUser", + "bouncerFullyShown", + "faceAndFpNotAuthenticated", + "faceAuthAllowed", + "faceDisabled", + "faceLockedOut", + "goingToSleep", + "keyguardAwake", + "keyguardGoingAway", + "listeningForFaceAssistant", + "occludingAppRequestingFaceAuth", + "primaryUser", + "secureCameraLaunched", + "supportsDetect", + "switchingUser", + "udfpsBouncerShowing", + "udfpsFingerDown", + "userNotTrustedOrDetectionIsNeeded", + ) + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt new file mode 100644 index 000000000000..998dc09aa5ab --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt @@ -0,0 +1,166 @@ +/* + * 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.keyguard + +import android.annotation.CurrentTimeMillisLong +import com.android.systemui.dump.DumpsysTableLogger +import com.android.systemui.dump.Row +import com.android.systemui.plugins.util.RingBuffer + +/** Verbose debug information. */ +data class KeyguardFingerprintListenModel( + @CurrentTimeMillisLong override var timeMillis: Long = 0L, + override var userId: Int = 0, + override var listening: Boolean = false, + // keepSorted + var biometricEnabledForUser: Boolean = false, + var bouncerIsOrWillShow: Boolean = false, + var canSkipBouncer: Boolean = false, + var credentialAttempted: Boolean = false, + var deviceInteractive: Boolean = false, + var dreaming: Boolean = false, + var fingerprintDisabled: Boolean = false, + var fingerprintLockedOut: Boolean = false, + var goingToSleep: Boolean = false, + var keyguardGoingAway: Boolean = false, + var keyguardIsVisible: Boolean = false, + var keyguardOccluded: Boolean = false, + var occludingAppRequestingFp: Boolean = false, + var primaryUser: Boolean = false, + var shouldListenSfpsState: Boolean = false, + var shouldListenForFingerprintAssistant: Boolean = false, + var strongerAuthRequired: Boolean = false, + var switchingUser: Boolean = false, + var udfps: Boolean = false, + var userDoesNotHaveTrust: Boolean = false, +) : KeyguardListenModel() { + + /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ + val asStringList: List<String> by lazy { + listOf( + DATE_FORMAT.format(timeMillis), + timeMillis.toString(), + userId.toString(), + listening.toString(), + // keep sorted + biometricEnabledForUser.toString(), + bouncerIsOrWillShow.toString(), + canSkipBouncer.toString(), + credentialAttempted.toString(), + deviceInteractive.toString(), + dreaming.toString(), + fingerprintDisabled.toString(), + fingerprintLockedOut.toString(), + goingToSleep.toString(), + keyguardGoingAway.toString(), + keyguardIsVisible.toString(), + keyguardOccluded.toString(), + occludingAppRequestingFp.toString(), + primaryUser.toString(), + shouldListenSfpsState.toString(), + shouldListenForFingerprintAssistant.toString(), + strongerAuthRequired.toString(), + switchingUser.toString(), + udfps.toString(), + userDoesNotHaveTrust.toString(), + ) + } + + /** + * [RingBuffer] to store [KeyguardFingerprintListenModel]. After the buffer is full, it will + * recycle old events. + * + * Do not use [append] to add new elements. Instead use [insert], as it will recycle if + * necessary. + */ + class Buffer { + private val buffer = RingBuffer(CAPACITY) { KeyguardFingerprintListenModel() } + + fun insert(model: KeyguardFingerprintListenModel) { + buffer.advance().apply { + timeMillis = model.timeMillis + userId = model.userId + listening = model.listening + // keep sorted + biometricEnabledForUser = model.biometricEnabledForUser + bouncerIsOrWillShow = model.bouncerIsOrWillShow + canSkipBouncer = model.canSkipBouncer + credentialAttempted = model.credentialAttempted + deviceInteractive = model.deviceInteractive + dreaming = model.dreaming + fingerprintDisabled = model.fingerprintDisabled + fingerprintLockedOut = model.fingerprintLockedOut + goingToSleep = model.goingToSleep + keyguardGoingAway = model.keyguardGoingAway + keyguardIsVisible = model.keyguardIsVisible + keyguardOccluded = model.keyguardOccluded + occludingAppRequestingFp = model.occludingAppRequestingFp + primaryUser = model.primaryUser + shouldListenSfpsState = model.shouldListenSfpsState + shouldListenForFingerprintAssistant = model.shouldListenForFingerprintAssistant + strongerAuthRequired = model.strongerAuthRequired + switchingUser = model.switchingUser + udfps = model.udfps + userDoesNotHaveTrust = model.userDoesNotHaveTrust + } + } + + /** + * Returns the content of the buffer (sorted from latest to newest). + * + * @see KeyguardFingerprintListenModel.asStringList + */ + fun toList(): List<Row> { + return buffer.asSequence().map { it.asStringList }.toList() + } + } + + companion object { + const val CAPACITY = 20 // number of logs to retain + + /** Headers for dumping a table using [DumpsysTableLogger]. */ + @JvmField + val TABLE_HEADERS = + listOf( + "timestamp", + "time_millis", + "userId", + "listening", + // keep sorted + "biometricAllowedForUser", + "bouncerIsOrWillShow", + "canSkipBouncer", + "credentialAttempted", + "deviceInteractive", + "dreaming", + "fingerprintDisabled", + "fingerprintLockedOut", + "goingToSleep", + "keyguardGoingAway", + "keyguardIsVisible", + "keyguardOccluded", + "occludingAppRequestingFp", + "primaryUser", + "shouldListenSidFingerprintState", + "shouldListenForFingerprintAssistant", + "strongAuthRequired", + "switchingUser", + "underDisplayFingerprint", + "userDoesNotHaveTrust", + ) + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 2b660dee4f16..d4ca8e34fb32 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -67,8 +67,12 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onTrustGrantedForCurrentUser(boolean dismissKeyguard, - TrustGrantFlags flags, String message) { + public void onTrustGrantedForCurrentUser( + boolean dismissKeyguard, + boolean newlyUnlocked, + TrustGrantFlags flags, + String message + ) { if (dismissKeyguard) { if (!mView.isVisibleToUser()) { // The trust agent dismissed the keyguard without the user proving diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 52ca1668e4c9..1296cf3b1f33 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -1,87 +1,32 @@ +/* + * 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.keyguard -import android.annotation.CurrentTimeMillisLong +import java.text.SimpleDateFormat +import java.util.Locale /** Verbose logging for various keyguard listening states. */ sealed class KeyguardListenModel { /** Timestamp of the state change. */ - abstract val timeMillis: Long + abstract var timeMillis: Long /** Current user. */ - abstract val userId: Int + abstract var userId: Int /** If keyguard is listening for the modality represented by this model. */ - abstract val listening: Boolean + abstract var listening: Boolean } -/** - * Verbose debug information associated with [KeyguardUpdateMonitor.shouldListenForFingerprint]. - */ -data class KeyguardFingerprintListenModel( - @CurrentTimeMillisLong override val timeMillis: Long, - override val userId: Int, - override val listening: Boolean, - // keep sorted - val biometricEnabledForUser: Boolean, - val bouncerIsOrWillShow: Boolean, - val canSkipBouncer: Boolean, - val credentialAttempted: Boolean, - val deviceInteractive: Boolean, - val dreaming: Boolean, - val fingerprintDisabled: Boolean, - val fingerprintLockedOut: Boolean, - val goingToSleep: Boolean, - val keyguardGoingAway: Boolean, - val keyguardIsVisible: Boolean, - val keyguardOccluded: Boolean, - val occludingAppRequestingFp: Boolean, - val primaryUser: Boolean, - val shouldListenSfpsState: Boolean, - val shouldListenForFingerprintAssistant: Boolean, - val strongerAuthRequired: Boolean, - val switchingUser: Boolean, - val udfps: Boolean, - val userDoesNotHaveTrust: Boolean -) : KeyguardListenModel() -/** - * Verbose debug information associated with [KeyguardUpdateMonitor.shouldListenForFace]. - */ -data class KeyguardFaceListenModel( - @CurrentTimeMillisLong override val timeMillis: Long, - override val userId: Int, - override val listening: Boolean, - // keep sorted - val authInterruptActive: Boolean, - val biometricSettingEnabledForUser: Boolean, - val bouncerFullyShown: Boolean, - val faceAndFpNotAuthenticated: Boolean, - val faceAuthAllowed: Boolean, - val faceDisabled: Boolean, - val faceLockedOut: Boolean, - val goingToSleep: Boolean, - val keyguardAwake: Boolean, - val keyguardGoingAway: Boolean, - val listeningForFaceAssistant: Boolean, - val occludingAppRequestingFaceAuth: Boolean, - val primaryUser: Boolean, - val secureCameraLaunched: Boolean, - val supportsDetect: Boolean, - val switchingUser: Boolean, - val udfpsBouncerShowing: Boolean, - val udfpsFingerDown: Boolean, - val userNotTrustedOrDetectionIsNeeded: Boolean, - ) : KeyguardListenModel() -/** - * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock]. - */ -data class KeyguardActiveUnlockModel( - @CurrentTimeMillisLong override val timeMillis: Long, - override val userId: Int, - override val listening: Boolean, - // keep sorted - val awakeKeyguard: Boolean, - val authInterruptActive: Boolean, - val fpLockedOut: Boolean, - val primaryAuthRequired: Boolean, - val switchingUser: Boolean, - val triggerActiveUnlockForAssistant: Boolean, - val userCanDismissLockScreen: Boolean -) : KeyguardListenModel() +val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt deleted file mode 100644 index 210f5e763911..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard - -import androidx.annotation.VisibleForTesting -import java.io.PrintWriter -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import kotlin.collections.ArrayDeque - -private val DEFAULT_FORMATTING = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) - -/** Queue for verbose logging checks for the listening state. */ -class KeyguardListenQueue( - val sizePerModality: Int = 20 -) { - private val faceQueue = ArrayDeque<KeyguardFaceListenModel>() - private val fingerprintQueue = ArrayDeque<KeyguardFingerprintListenModel>() - private val activeUnlockQueue = ArrayDeque<KeyguardActiveUnlockModel>() - - @get:VisibleForTesting val models: List<KeyguardListenModel> - get() = faceQueue + fingerprintQueue + activeUnlockQueue - - /** Push a [model] to the queue (will be logged until the queue exceeds [sizePerModality]). */ - fun add(model: KeyguardListenModel) { - val queue = when (model) { - is KeyguardFaceListenModel -> faceQueue.apply { add(model) } - is KeyguardFingerprintListenModel -> fingerprintQueue.apply { add(model) } - is KeyguardActiveUnlockModel -> activeUnlockQueue.apply { add(model) } - } - - if (queue.size > sizePerModality) { - queue.removeFirstOrNull() - } - } - - /** Print verbose logs via the [writer]. */ - @JvmOverloads - fun print(writer: PrintWriter, dateFormat: DateFormat = DEFAULT_FORMATTING) { - val stringify: (KeyguardListenModel) -> String = { model -> - " ${dateFormat.format(Date(model.timeMillis))} $model" - } - - writer.println(" Face listen results (last ${faceQueue.size} calls):") - for (model in faceQueue) { - writer.println(stringify(model)) - } - writer.println(" Fingerprint listen results (last ${fingerprintQueue.size} calls):") - for (model in fingerprintQueue) { - writer.println(stringify(model)) - } - writer.println(" Active unlock triggers (last ${activeUnlockQueue.size} calls):") - for (model in activeUnlockQueue) { - writer.println(stringify(model)) - } - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 67e3400670ba..061ca4f7d850 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -177,8 +177,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { @Override public void startAppearAnimation() { - setAlpha(1f); - setTranslationY(0); if (mAppearAnimator.isRunning()) { mAppearAnimator.cancel(); } @@ -215,6 +213,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { /** Animate subviews according to expansion or time. */ private void animate(float progress) { + setAlpha(progress); Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE; Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8283ce8d3a8d..2c3d3a0e0a64 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -146,6 +146,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; @@ -461,14 +462,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray(); private final Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<>(); + private final KeyguardFingerprintListenModel.Buffer mFingerprintListenBuffer = + new KeyguardFingerprintListenModel.Buffer(); + private final KeyguardFaceListenModel.Buffer mFaceListenBuffer = + new KeyguardFaceListenModel.Buffer(); + private final KeyguardActiveUnlockModel.Buffer mActiveUnlockTriggerBuffer = + new KeyguardActiveUnlockModel.Buffer(); + @VisibleForTesting SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>(); @VisibleForTesting SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>(); - // Keep track of recent calls to shouldListenFor*() for debugging. - private final KeyguardListenQueue mListenModels = new KeyguardListenQueue(); - private static int sCurrentUser; public synchronized static void setCurrentUser(int currentUser) { @@ -510,7 +515,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - mLogger.logTrustGrantedWithFlags(flags, userId, message); + mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message); if (userId == getCurrentUser()) { final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags); for (int i = 0; i < mCallbacks.size(); i++) { @@ -518,7 +523,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (cb != null) { cb.onTrustGrantedForCurrentUser( shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags), - trustGrantFlags, message); + newlyUnlocked, + trustGrantFlags, + message + ); } } } @@ -2647,7 +2655,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mSecureCameraLaunched; // Aggregate relevant fields for debug logging. - maybeLogListenerModelData( + logListenerModelData( new KeyguardActiveUnlockModel( System.currentTimeMillis(), user, @@ -2728,7 +2736,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean shouldListen = shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState && shouldListenUdfpsState && shouldListenSideFpsState; - maybeLogListenerModelData( + logListenerModelData( new KeyguardFingerprintListenModel( System.currentTimeMillis(), user, @@ -2813,7 +2821,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mGoingToSleep; // Aggregate relevant fields for debug logging. - maybeLogListenerModelData( + logListenerModelData( new KeyguardFaceListenModel( System.currentTimeMillis(), user, @@ -2841,28 +2849,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return shouldListen; } - private void maybeLogListenerModelData(@NonNull KeyguardListenModel model) { + private void logListenerModelData(@NonNull KeyguardListenModel model) { mLogger.logKeyguardListenerModel(model); - - if (model instanceof KeyguardActiveUnlockModel) { - mListenModels.add(model); - return; - } - - // Add model data to the historical buffer. - final boolean notYetRunning = - (model instanceof KeyguardFaceListenModel - && mFaceRunningState != BIOMETRIC_STATE_RUNNING) - || (model instanceof KeyguardFingerprintListenModel - && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING); - final boolean running = - (model instanceof KeyguardFaceListenModel - && mFaceRunningState == BIOMETRIC_STATE_RUNNING) - || (model instanceof KeyguardFingerprintListenModel - && mFingerprintRunningState == BIOMETRIC_STATE_RUNNING); - if (notYetRunning && model.getListening() - || running && !model.getListening()) { - mListenModels.add(model); + if (model instanceof KeyguardFingerprintListenModel) { + mFingerprintListenBuffer.insert((KeyguardFingerprintListenModel) model); + } else if (model instanceof KeyguardActiveUnlockModel) { + mActiveUnlockTriggerBuffer.insert((KeyguardActiveUnlockModel) model); + } else if (model instanceof KeyguardFaceListenModel) { + mFaceListenBuffer.insert((KeyguardFaceListenModel) model); } } @@ -3935,6 +3929,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled=" + mSfpsRequireScreenOnToAuthPrefEnabled); } + new DumpsysTableLogger( + "KeyguardFingerprintListen", + KeyguardFingerprintListenModel.TABLE_HEADERS, + mFingerprintListenBuffer.toList() + ).printTableData(pw); } if (mFaceManager != null && mFaceManager.isHardwareDetected()) { final int userId = mUserTracker.getUserId(); @@ -3960,7 +3959,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched); pw.println(" mPrimaryBouncerFullyShown=" + mPrimaryBouncerFullyShown); pw.println(" mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition); - } - mListenModels.print(pw); + new DumpsysTableLogger( + "KeyguardFaceListen", + KeyguardFaceListenModel.TABLE_HEADERS, + mFaceListenBuffer.toList() + ).printTableData(pw); + } + + new DumpsysTableLogger( + "KeyguardActiveUnlockTriggers", + KeyguardActiveUnlockModel.TABLE_HEADERS, + mActiveUnlockTriggerBuffer.toList() + ).printTableData(pw); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 1d58fc9cf94b..e6b9ac8b38a8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -178,11 +178,17 @@ public class KeyguardUpdateMonitorCallback { * Called after trust was granted. * @param dismissKeyguard whether the keyguard should be dismissed as a result of the * trustGranted + * @param newlyUnlocked whether the grantedTrust is believed to be the cause of a newly + * unlocked device (after being locked). * @param message optional message the trust agent has provided to show that should indicate * why trust was granted. */ - public void onTrustGrantedForCurrentUser(boolean dismissKeyguard, - @NonNull TrustGrantFlags flags, @Nullable String message) { } + public void onTrustGrantedForCurrentUser( + boolean dismissKeyguard, + boolean newlyUnlocked, + @NonNull TrustGrantFlags flags, + @Nullable String message + ) { } /** * Called when a biometric has been acquired. diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index ceebe4c69091..21d3b24174b6 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -379,14 +379,17 @@ class KeyguardUpdateMonitorLogger @Inject constructor( fun logTrustGrantedWithFlags( flags: Int, + newlyUnlocked: Boolean, userId: Int, message: String? ) { logBuffer.log(TAG, DEBUG, { int1 = flags + bool1 = newlyUnlocked int2 = userId str1 = message - }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" }) + }, { "trustGrantedWithFlags[user=$int2] newlyUnlocked=$bool1 " + + "flags=${TrustGrantFlags(int1)} message=$str1" }) } fun logTrustChanged( diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt index 4b7e9a57ef6a..98ac2c0bd026 100644 --- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt +++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt @@ -16,31 +16,25 @@ package com.android.keyguard.mediator +import android.annotation.BinderThread import android.os.Trace - import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.ScreenLifecycle -import com.android.systemui.util.concurrency.Execution -import com.android.systemui.util.concurrency.PendingTasksContainer import com.android.systemui.unfold.SysUIUnfoldComponent +import com.android.systemui.util.concurrency.PendingTasksContainer import com.android.systemui.util.kotlin.getOrNull - import java.util.Optional - import javax.inject.Inject /** * Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for * screen on events, this will invoke the onDrawn Runnable after all tasks have completed. This - * should route back to the KeyguardService, which informs the system_server that keyguard has - * drawn. + * should route back to the [com.android.systemui.keyguard.KeyguardService], which informs + * the system_server that keyguard has drawn. */ @SysUISingleton class ScreenOnCoordinator @Inject constructor( - screenLifecycle: ScreenLifecycle, - unfoldComponent: Optional<SysUIUnfoldComponent>, - private val execution: Execution -) : ScreenLifecycle.Observer { + unfoldComponent: Optional<SysUIUnfoldComponent> +) { private val unfoldLightRevealAnimation = unfoldComponent.map( SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation).getOrNull() @@ -48,15 +42,12 @@ class ScreenOnCoordinator @Inject constructor( SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull() private val pendingTasks = PendingTasksContainer() - init { - screenLifecycle.addObserver(this) - } - /** * When turning on, registers tasks that may need to run before invoking [onDrawn]. + * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService]. */ - override fun onScreenTurningOn(onDrawn: Runnable) { - execution.assertIsMainThread() + @BinderThread + fun onScreenTurningOn(onDrawn: Runnable) { Trace.beginSection("ScreenOnCoordinator#onScreenTurningOn") pendingTasks.reset() @@ -68,11 +59,13 @@ class ScreenOnCoordinator @Inject constructor( Trace.endSection() } - override fun onScreenTurnedOn() { - execution.assertIsMainThread() - + /** + * Called when screen is fully turned on and screen on blocker is removed. + * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService]. + */ + @BinderThread + fun onScreenTurnedOn() { foldAodAnimationController?.onScreenTurnedOn() - pendingTasks.reset() } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index a4252bbe1d68..30117d988091 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -40,6 +40,7 @@ import com.android.systemui.reardisplay.RearDisplayDialogController import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher +import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController import com.android.systemui.stylus.StylusUsiPowerStartable @@ -79,6 +80,12 @@ abstract class SystemUICoreStartableModule { @ClassKey(ClipboardListener::class) abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable + /** Inject into FsiChromeRepo. */ + @Binds + @IntoMap + @ClassKey(FsiChromeRepo::class) + abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable + /** Inject into GarbageMonitor.Service. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d58743bd259f..ee9b804ec30f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -165,7 +165,9 @@ object Flags { /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */ // TODO(b/256513609): Tracking Bug - @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar") + @JvmField + val ACTIVE_UNLOCK_CHIPBAR = + resourceBooleanFlag(217, R.bool.flag_active_unlock_chipbar, "active_unlock_chipbar") /** * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the @@ -396,6 +398,10 @@ object Flags { // TODO(b/254512756): Tracking Bug val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc") + // TODO(b/261979569): Tracking Bug + val QUICK_TAP_FLOW_FRAMEWORK = + unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false) + // 1500 - chooser // TODO(b/254512507): Tracking Bug val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java index 822b1cfdf877..757afb616fd1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java @@ -18,11 +18,8 @@ package com.android.systemui.keyguard; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; import android.os.Trace; -import android.util.Log; -import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.systemui.dagger.SysUISingleton; import javax.inject.Inject; @@ -80,33 +77,10 @@ public class KeyguardLifecyclesDispatcher { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - final Object obj = msg.obj; switch (msg.what) { case SCREEN_TURNING_ON: Trace.beginSection("KeyguardLifecyclesDispatcher#SCREEN_TURNING_ON"); - final String onDrawWaitingTraceTag = - "Waiting for KeyguardDrawnCallback#onDrawn"; - int traceCookie = System.identityHashCode(msg); - Trace.beginAsyncSection(onDrawWaitingTraceTag, traceCookie); - // Ensure the drawn callback is only ever called once - mScreenLifecycle.dispatchScreenTurningOn(new Runnable() { - boolean mInvoked; - @Override - public void run() { - if (obj == null) return; - if (!mInvoked) { - mInvoked = true; - try { - Trace.endAsyncSection(onDrawWaitingTraceTag, traceCookie); - ((IKeyguardDrawnCallback) obj).onDrawn(); - } catch (RemoteException e) { - Log.w(TAG, "Exception calling onDrawn():", e); - } - } else { - Log.w(TAG, "KeyguardDrawnCallback#onDrawn() invoked > 1 times"); - } - } - }); + mScreenLifecycle.dispatchScreenTurningOn(); Trace.endSection(); break; case SCREEN_TURNED_ON: diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index c332a0d66294..f4a1227a467c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -78,6 +78,7 @@ import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.internal.policy.IKeyguardStateCallback; +import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SystemUIApplication; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; @@ -120,6 +121,7 @@ public class KeyguardService extends Service { private final KeyguardViewMediator mKeyguardViewMediator; private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher; + private final ScreenOnCoordinator mScreenOnCoordinator; private final ShellTransitions mShellTransitions; private static int newModeToLegacyMode(int newMode) { @@ -283,10 +285,12 @@ public class KeyguardService extends Service { @Inject public KeyguardService(KeyguardViewMediator keyguardViewMediator, KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher, + ScreenOnCoordinator screenOnCoordinator, ShellTransitions shellTransitions) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; + mScreenOnCoordinator = screenOnCoordinator; mShellTransitions = shellTransitions; } @@ -583,6 +587,31 @@ public class KeyguardService extends Service { checkPermission(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON, callback); + + final String onDrawWaitingTraceTag = "Waiting for KeyguardDrawnCallback#onDrawn"; + final int traceCookie = System.identityHashCode(callback); + Trace.beginAsyncSection(onDrawWaitingTraceTag, traceCookie); + + // Ensure the drawn callback is only ever called once + mScreenOnCoordinator.onScreenTurningOn(new Runnable() { + boolean mInvoked; + @Override + public void run() { + if (callback == null) return; + if (!mInvoked) { + mInvoked = true; + try { + Trace.endAsyncSection(onDrawWaitingTraceTag, traceCookie); + callback.onDrawn(); + } catch (RemoteException e) { + Log.w(TAG, "Exception calling onDrawn():", e); + } + } else { + Log.w(TAG, "KeyguardDrawnCallback#onDrawn() invoked > 1 times"); + } + } + }); + Trace.endSection(); } @@ -591,6 +620,7 @@ public class KeyguardService extends Service { Trace.beginSection("KeyguardService.mBinder#onScreenTurnedOn"); checkPermission(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_ON); + mScreenOnCoordinator.onScreenTurnedOn(); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 96ec43dd725e..d231870997f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -723,7 +723,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void keyguardDone(boolean strongAuth, int targetUserId) { - if (targetUserId != mUserTracker.getUserId()) { + if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) { return; } if (DEBUG) Log.d(TAG, "keyguardDone"); @@ -746,7 +746,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void keyguardDonePending(boolean strongAuth, int targetUserId) { Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending"); if (DEBUG) Log.d(TAG, "keyguardDonePending"); - if (targetUserId != mUserTracker.getUserId()) { + if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) { Trace.endSection(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt index 0a55294dfe8a..0b04fb43c2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt @@ -46,7 +46,7 @@ class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenL listeners.forEach(ScreenListener::onScreenTurningOff) } - override fun onScreenTurningOn(ignored: Runnable) { + override fun onScreenTurningOn() { listeners.forEach(ScreenListener::onScreenTurningOn) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java index b3481219a85d..8535eda93f96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java @@ -18,8 +18,6 @@ package com.android.systemui.keyguard; import android.os.Trace; -import androidx.annotation.NonNull; - import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; @@ -50,14 +48,9 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme return mScreenState; } - /** - * Dispatch screen turning on events to the registered observers - * - * @param onDrawn Invoke to notify the caller that the event has been processed - */ - public void dispatchScreenTurningOn(@NonNull Runnable onDrawn) { + public void dispatchScreenTurningOn() { setScreenState(SCREEN_TURNING_ON); - dispatch(Observer::onScreenTurningOn, onDrawn); + dispatch(Observer::onScreenTurningOn); } public void dispatchScreenTurnedOn() { @@ -87,12 +80,7 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme } public interface Observer { - /** - * Receive the screen turning on event - * - * @param onDrawn Invoke to notify the caller that the event has been processed - */ - default void onScreenTurningOn(@NonNull Runnable onDrawn) {} + default void onScreenTurningOn() {} default void onScreenTurnedOn() {} default void onScreenTurningOff() {} default void onScreenTurnedOff() {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 57668c795d1c..f4c335b04f5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -34,7 +34,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject @@ -333,9 +333,13 @@ constructor( fun getPickerFlags(): List<KeyguardPickerFlag> { return listOf( KeyguardPickerFlag( - name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED, + name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), - ) + ), + KeyguardPickerFlag( + name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, + value = featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), + ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index 3218f9699f35..5cb7d709a1a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -22,7 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED +import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.WakefulnessState @@ -50,27 +50,22 @@ constructor( override fun start() { listenForDraggingUpToBouncer() - listenForBouncerHiding() + listenForBouncer() } - private fun listenForBouncerHiding() { + private fun listenForBouncer() { scope.launch { keyguardInteractor.isBouncerShowing .sample( combine( keyguardInteractor.wakefulnessModel, keyguardTransitionInteractor.startedKeyguardTransitionStep, - ) { wakefulnessModel, transitionStep -> - Pair(wakefulnessModel, transitionStep) - } - ) { bouncerShowing, wakefulnessAndTransition -> - Triple( - bouncerShowing, - wakefulnessAndTransition.first, - wakefulnessAndTransition.second - ) - } - .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) -> + ::Pair + ), + ::toTriple + ) + .collect { triple -> + val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple if ( !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER ) { @@ -91,7 +86,19 @@ constructor( animator = getAnimator(), ) ) + } else if ( + isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.BOUNCER, + animator = getAnimator(), + ) + ) } + Unit } } } @@ -104,24 +111,20 @@ constructor( combine( keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - ) { finishedKeyguardState, statusBarState -> - Pair(finishedKeyguardState, statusBarState) - } - ) { shadeModel, keyguardStateAndStatusBarState -> - Triple( - shadeModel, - keyguardStateAndStatusBarState.first, - keyguardStateAndStatusBarState.second - ) - } - .collect { (shadeModel, keyguardState, statusBarState) -> + ::Pair + ), + ::toTriple + ) + .collect { triple -> + val (shadeModel, keyguardState, statusBarState) = triple + val id = transitionId if (id != null) { // An existing `id` means a transition is started, and calls to // `updateTransition` will control it until FINISHED keyguardTransitionRepository.updateTransition( id, - shadeModel.expansionAmount, + 1f - shadeModel.expansionAmount, if ( shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f ) { @@ -137,7 +140,7 @@ constructor( if ( keyguardState == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && - statusBarState != SHADE_LOCKED + statusBarState == KEYGUARD ) { transitionId = keyguardTransitionRepository.startTransition( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index 2cf5fb98d07e..2a3a33eff274 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -77,7 +77,6 @@ constructor( /** Runnable to show the primary bouncer. */ val showRunnable = Runnable { - repository.setPrimaryVisible(true) repository.setPrimaryShow( KeyguardBouncerModel( promptReason = repository.bouncerPromptReason ?: 0, @@ -86,6 +85,7 @@ constructor( ) ) repository.setPrimaryShowingSoon(false) + repository.setPrimaryVisible(true) primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE) } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index ec2e340762a3..48a68be962dd 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -123,7 +123,7 @@ public class LogModule { @SysUISingleton @QSLog public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) { - return factory.create("QSLog", 500 /* maxSize */, false /* systrace */); + return factory.create("QSLog", 700 /* maxSize */, false /* systrace */); } /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d762b39fa1af..db7d66edc4b2 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -1290,8 +1290,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } private void onVerticalChanged(boolean isVertical) { - mCentralSurfacesOptionalLazy.get().ifPresent(statusBar -> - statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical)); + Optional<CentralSurfaces> cs = mCentralSurfacesOptionalLazy.get(); + if (cs.isPresent() && cs.get().getNotificationPanelViewController() != null) { + cs.get().getNotificationPanelViewController().setQsScrimEnabled(!isVertical); + } } private boolean onNavigationTouch(View v, MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 8ceee1a950ea..7523d6e976ce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -11,7 +11,6 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.AttributeSet; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -31,6 +30,7 @@ import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; +import com.android.systemui.qs.logging.QSLogger; import java.util.ArrayList; import java.util.List; @@ -38,11 +38,9 @@ import java.util.Set; public class PagedTileLayout extends ViewPager implements QSTileLayout { - private static final boolean DEBUG = false; private static final String CURRENT_PAGE = "current_page"; private static final int NO_PAGE = -1; - private static final String TAG = "PagedTileLayout"; private static final int REVEAL_SCROLL_DURATION_MILLIS = 750; private static final float BOUNCE_ANIMATION_TENSION = 1.3f; private static final long BOUNCE_ANIMATION_DURATION = 450L; @@ -55,6 +53,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private final ArrayList<TileRecord> mTiles = new ArrayList<>(); private final ArrayList<TileLayout> mPages = new ArrayList<>(); + private QSLogger mLogger; @Nullable private PageIndicator mPageIndicator; private float mPageIndicatorPosition; @@ -146,9 +145,15 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } if (mLayoutOrientation != newConfig.orientation) { mLayoutOrientation = newConfig.orientation; - mDistributeTiles = true; + forceTilesRedistribution("orientation changed to " + mLayoutOrientation); setCurrentItem(0, false); mPageToRestore = 0; + } else { + // logging in case we missed redistribution because orientation was not changed + // while configuration changed, can be removed after b/255208946 is fixed + mLogger.d( + "Orientation didn't change, tiles might be not redistributed, new config", + newConfig); } } @@ -226,7 +231,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { // Keep on drawing until the animation has finished. postInvalidateOnAnimation(); } catch (NullPointerException e) { - Log.e(TAG, "FakeDragBy called before begin", e); + mLogger.logException("FakeDragBy called before begin", e); // If we were trying to fake drag, it means we just added a new tile to the last // page, so animate there. final int lastPageNumber = mPages.size() - 1; @@ -246,7 +251,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { super.endFakeDrag(); } catch (NullPointerException e) { // Not sure what's going on. Let's log it - Log.e(TAG, "endFakeDrag called without velocityTracker", e); + mLogger.logException("endFakeDrag called without velocityTracker", e); } } @@ -304,14 +309,14 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public void addTile(TileRecord tile) { mTiles.add(tile); - mDistributeTiles = true; + forceTilesRedistribution("adding new tile"); requestLayout(); } @Override public void removeTile(TileRecord tile) { if (mTiles.remove(tile)) { - mDistributeTiles = true; + forceTilesRedistribution("removing tile"); requestLayout(); } } @@ -367,19 +372,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { final int tilesPerPageCount = mPages.get(0).maxTiles(); int index = 0; final int totalTilesCount = mTiles.size(); - if (DEBUG) { - Log.d(TAG, "Distributing tiles: " - + "[tilesPerPageCount=" + tilesPerPageCount + "]" - + "[totalTilesCount=" + totalTilesCount + "]" - ); - } + mLogger.logTileDistributionInProgress(tilesPerPageCount, totalTilesCount); for (int i = 0; i < totalTilesCount; i++) { TileRecord tile = mTiles.get(i); if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++; - if (DEBUG) { - Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to " - + index); - } + mLogger.logTileDistributed(tile.tile.getClass().getSimpleName(), index); mPages.get(index).addTile(tile); } } @@ -394,11 +391,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return; } while (mPages.size() < numPages) { - if (DEBUG) Log.d(TAG, "Adding page"); + mLogger.d("Adding new page"); mPages.add(createTileLayout()); } while (mPages.size() > numPages) { - if (DEBUG) Log.d(TAG, "Removing page"); + mLogger.d("Removing page"); mPages.remove(mPages.size() - 1); } mPageIndicator.setNumPages(mPages.size()); @@ -417,8 +414,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { changed |= mPages.get(i).updateResources(); } if (changed) { - mDistributeTiles = true; + forceTilesRedistribution("resources in pages changed"); requestLayout(); + } else { + // logging in case we missed redistribution because number of column in updateResources + // was not changed, can be removed after b/255208946 is fixed + mLogger.d("resource in pages didn't change, tiles might be not redistributed"); } return changed; } @@ -430,7 +431,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { for (int i = 0; i < mPages.size(); i++) { if (mPages.get(i).setMinRows(minRows)) { changed = true; - mDistributeTiles = true; + forceTilesRedistribution("minRows changed in page"); } } return changed; @@ -443,7 +444,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { for (int i = 0; i < mPages.size(); i++) { if (mPages.get(i).setMaxColumns(maxColumns)) { changed = true; - mDistributeTiles = true; + forceTilesRedistribution("maxColumns in pages changed"); } } return changed; @@ -710,14 +711,14 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private final PagerAdapter mAdapter = new PagerAdapter() { @Override public void destroyItem(ViewGroup container, int position, Object object) { - if (DEBUG) Log.d(TAG, "Destantiating " + position); + mLogger.d("Destantiating page at", position); container.removeView((View) object); updateListening(); } @Override public Object instantiateItem(ViewGroup container, int position) { - if (DEBUG) Log.d(TAG, "Instantiating " + position); + mLogger.d("Instantiating page at", position); if (isLayoutRtl()) { position = mPages.size() - 1 - position; } @@ -745,10 +746,15 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { * Force all tiles to be redistributed across pages. * Should be called when one of the following changes: rows, columns, number of tiles. */ - public void forceTilesRedistribution() { + public void forceTilesRedistribution(String reason) { + mLogger.d("forcing tile redistribution across pages, reason", reason); mDistributeTiles = true; } + public void setLogger(QSLogger qsLogger) { + mLogger = qsLogger; + } + public interface PageListener { int INVALID_PAGE = -1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 6517ff33a49d..7067c220da87 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -43,6 +43,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -106,6 +107,7 @@ public class QSPanel extends LinearLayout implements Tunable { private ViewGroup mMediaHostView; private boolean mShouldMoveMediaOnExpansion = true; private boolean mUsingCombinedHeaders = false; + private QSLogger mQsLogger; public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -122,7 +124,8 @@ public class QSPanel extends LinearLayout implements Tunable { } - void initialize() { + void initialize(QSLogger qsLogger) { + mQsLogger = qsLogger; mTileLayout = getOrCreateTileLayout(); if (mUsingMediaPlayer) { @@ -206,6 +209,7 @@ public class QSPanel extends LinearLayout implements Tunable { if (mTileLayout == null) { mTileLayout = (QSTileLayout) LayoutInflater.from(mContext) .inflate(R.layout.qs_paged_tile_layout, this, false); + mTileLayout.setLogger(mQsLogger); mTileLayout.setSquishinessFraction(mSquishinessFraction); } return mTileLayout; @@ -735,6 +739,8 @@ public class QSPanel extends LinearLayout implements Tunable { default void setExpansion(float expansion, float proposedTranslation) {} int getNumVisibleTiles(); + + default void setLogger(QSLogger qsLogger) { } } interface OnConfigurationChangedListener { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index b2ca6b728113..cabe1daf16f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -122,9 +122,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } switchTileLayout(true); mBrightnessMirrorHandler.onQsPanelAttached(); - - ((PagedTileLayout) mView.getOrCreateTileLayout()) - .setOnTouchListener(mTileLayoutTouchListener); + PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout()); + pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener); } @Override @@ -150,7 +149,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override protected void onSplitShadeChanged() { - ((PagedTileLayout) mView.getOrCreateTileLayout()).forceTilesRedistribution(); + ((PagedTileLayout) mView.getOrCreateTileLayout()) + .forceTilesRedistribution("Split shade state changed"); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 60d2c177c7cd..7bb672ce5880 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -70,7 +70,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr protected final MediaHost mMediaHost; protected final MetricsLogger mMetricsLogger; private final UiEventLogger mUiEventLogger; - private final QSLogger mQSLogger; + protected final QSLogger mQSLogger; private final DumpManager mDumpManager; protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); protected boolean mShouldUseSplitNotificationShade; @@ -152,7 +152,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr @Override protected void onInit() { - mView.initialize(); + mView.initialize(mQSLogger); mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), ""); } @@ -430,6 +430,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr pw.println(" horizontal layout: " + mUsingHorizontalLayout); pw.println(" last orientation: " + mLastOrientation); } + pw.println(" mShouldUseSplitNotificationShade: " + mShouldUseSplitNotificationShade); } public QSPanel.QSTileLayout getTileLayout() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 9f6317fd931b..d682853f5fef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -21,10 +21,13 @@ import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel.DEBUG +import com.android.systemui.plugins.log.LogLevel.ERROR import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.plugins.log.LogMessage import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.StatusBarState +import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject private const val TAG = "QSLog" @@ -33,6 +36,26 @@ class QSLogger @Inject constructor( @QSLog private val buffer: LogBuffer ) { + fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg) + + fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg) + + fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg) + + fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg) + + fun logException(@CompileTimeConstant logMsg: String, ex: Exception) { + buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex) + } + + fun v(@CompileTimeConstant msg: String, arg: Any) { + buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" }) + } + + fun d(@CompileTimeConstant msg: String, arg: Any) { + buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" }) + } + fun logTileAdded(tileSpec: String) { log(DEBUG, { str1 = tileSpec @@ -236,6 +259,24 @@ class QSLogger @Inject constructor( }) } + fun logTileDistributionInProgress(tilesPerPageCount: Int, totalTilesCount: Int) { + log(DEBUG, { + int1 = tilesPerPageCount + int2 = totalTilesCount + }, { + "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" + }) + } + + fun logTileDistributed(tileName: String, pageIndex: Int) { + log(DEBUG, { + str1 = tileName + int1 = pageIndex + }, { + "Adding $str1 to page number $int1" + }) + } + private fun toStateString(state: Int): String { return when (state) { Tile.STATE_ACTIVE -> "active" diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 00d129ae70ca..4d005bebd99e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -889,7 +889,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis * Notifies the Launcher that screen is starting to turn on. */ @Override - public void onScreenTurningOn(@NonNull Runnable ignored) { + public void onScreenTurningOn() { try { if (mOverviewProxy != null) { mOverviewProxy.onScreenTurningOn(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 6d6427acec4c..84b836fec149 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4994,10 +4994,37 @@ public final class NotificationPanelViewController implements Dumpable { return mExpandedFraction; } + /** + * This method should not be used anymore, you should probably use {@link #isShadeFullyOpen()} + * instead. It was overused as indicating if shade is open or we're on keyguard/AOD. + * Moving forward we should be explicit about the what state we're checking. + * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD + * + * @deprecated depends on the state you check, use {@link #isShadeFullyOpen()}, + * {@link #isOnAod()}, {@link #isOnKeyguard()} instead. + */ + @Deprecated public boolean isFullyExpanded() { return mExpandedHeight >= getMaxPanelTransitionDistance(); } + /** + * Returns true if shade is fully opened, that is we're actually in the notification shade + * with QQS or QS. It's different from {@link #isFullyExpanded()} that it will not report + * shade as always expanded if we're on keyguard/AOD. It will return true only when user goes + * from keyguard to shade. + */ + public boolean isShadeFullyOpen() { + if (mBarState == SHADE) { + return isFullyExpanded(); + } else if (mBarState == SHADE_LOCKED) { + return true; + } else { + // case of two finger swipe from the top of keyguard + return computeQsExpansionFraction() == 1; + } + } + public boolean isFullyCollapsed() { return mExpandedFraction <= 0.0f; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index a41a15d4e2a2..ad5a68e4dc3f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -63,8 +63,14 @@ public interface ShadeController { */ boolean closeShadeIfOpen(); - /** Returns whether the shade is currently open or opening. */ - boolean isShadeOpen(); + /** + * Returns whether the shade is currently open. + * Even though in the current implementation shade is in expanded state on keyguard, this + * method makes distinction between shade being truly open and plain keyguard state: + * - if QS and notifications are visible on the screen, return true + * - for any other state, including keyguard, return false + */ + boolean isShadeFullyOpen(); /** * Add a runnable for NotificationPanelView to post when the panel is expanded. diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index 638e74883f23..026673adb86b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -154,9 +154,8 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public boolean isShadeOpen() { - return mNotificationPanelViewController.isExpanding() - || mNotificationPanelViewController.isFullyExpanded(); + public boolean isShadeFullyOpen() { + return mNotificationPanelViewController.isShadeFullyOpen(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java index 959c339ab3e5..f87a1ed57d15 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java @@ -16,14 +16,17 @@ package com.android.systemui.shade; -import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.shade.data.repository.ShadeRepository; +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl; import dagger.Binds; import dagger.Module; -/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */ +/** Provides Shade-related events and information. */ @Module public abstract class ShadeEventsModule { @Binds abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl); + + @Binds abstract ShadeRepository shadeRepository(ShadeRepositoryImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 09019a69df47..bf7a2be2e4ca 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -27,11 +27,17 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +interface ShadeRepository { + /** ShadeModel information regarding shade expansion events */ + val shadeModel: Flow<ShadeModel> +} + /** Business logic for shade interactions */ @SysUISingleton -class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) { - - val shadeModel: Flow<ShadeModel> = +class ShadeRepositoryImpl +@Inject +constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepository { + override val shadeModel: Flow<ShadeModel> = conflatedCallbackFlow { val callback = object : ShadeExpansionListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 0f27420e22b0..b7001e476dcf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1193,8 +1193,12 @@ public class KeyguardIndicationController { } @Override - public void onTrustGrantedForCurrentUser(boolean dismissKeyguard, - @NonNull TrustGrantFlags flags, @Nullable String message) { + public void onTrustGrantedForCurrentUser( + boolean dismissKeyguard, + boolean newlyUnlocked, + @NonNull TrustGrantFlags flags, + @Nullable String message + ) { showTrustGrantedMessage(dismissKeyguard, message); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt index 0eb00008e289..dc9b41690d61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt @@ -306,9 +306,12 @@ interface Roundable { */ class RoundableState( internal val targetView: View, - roundable: Roundable, - internal val maxRadius: Float, + private val roundable: Roundable, + maxRadius: Float, ) { + internal var maxRadius = maxRadius + private set + /** Animatable for top roundness */ private val topAnimatable = topAnimatable(roundable) @@ -356,6 +359,13 @@ class RoundableState( PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated) } + fun setMaxRadius(radius: Float) { + if (maxRadius != radius) { + maxRadius = radius + roundable.applyRoundnessAndInvalidate() + } + } + fun debugString() = buildString { append("TargetView: ${targetView.hashCode()} ") append("Top: $topRoundness ") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt new file mode 100644 index 000000000000..b48322822c86 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt @@ -0,0 +1,102 @@ +package com.android.systemui.statusbar.notification.fsi + +import android.app.PendingIntent +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.RemoteException +import android.service.dreams.IDreamManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider +import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** + * Class that bridges the gap between clean app architecture and existing code. Provides new + * implementation of StatusBarNotificationActivityStarter launchFullscreenIntent that pipes + * one-directional data => FsiChromeViewModel => FsiChromeView. + */ +@SysUISingleton +class FsiChromeRepo +@Inject +constructor( + private val context: Context, + private val pm: PackageManager, + private val keyguardRepo: KeyguardRepository, + private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider, + private val featureFlags: FeatureFlags, + private val uiBgExecutor: Executor, + private val dreamManager: IDreamManager, + private val centralSurfaces: CentralSurfaces +) : CoreStartable { + + companion object { + private const val classTag = "FsiChromeRepo" + } + + data class FSIInfo( + val appName: String, + val appIcon: Drawable, + val fullscreenIntent: PendingIntent + ) + + private val _infoFlow = MutableStateFlow<FSIInfo?>(null) + val infoFlow: StateFlow<FSIInfo?> = _infoFlow + + override fun start() { + log("$classTag start listening for FSI notifications") + + // Listen for FSI launch events for the lifetime of SystemUI. + launchFullScreenIntentProvider.registerListener { entry -> launchFullscreenIntent(entry) } + } + + fun dismiss() { + _infoFlow.value = null + } + + fun onFullscreen() { + // TODO(b/243421660) implement transition from container to fullscreen + } + + fun stopScreenSaver() { + uiBgExecutor.execute { + try { + dreamManager.awaken() + } catch (e: RemoteException) { + e.printStackTrace() + } + } + } + + fun launchFullscreenIntent(entry: NotificationEntry) { + if (!featureFlags.isEnabled(Flags.FSI_CHROME)) { + return + } + if (!keyguardRepo.isKeyguardShowing()) { + return + } + stopScreenSaver() + + var appName = pm.getApplicationLabel(context.applicationInfo) as String + val appIcon = pm.getApplicationIcon(context.packageName) + val fullscreenIntent = entry.sbn.notification.fullScreenIntent + + log("FsiChromeRepo launchFullscreenIntent appName=$appName appIcon $appIcon") + _infoFlow.value = FSIInfo(appName, appIcon, fullscreenIntent) + + // If screen is off or we're showing AOD, show lockscreen. + centralSurfaces.wakeUpForFullScreenIntent() + + // Don't show HUN since we're already showing FSI. + entry.notifyFullScreenIntentLaunched() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt index 0380fff1e2af..1fcf17fe7553 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt @@ -17,10 +17,14 @@ package com.android.systemui.statusbar.notification.logging +import android.app.Notification + /** Describes usage of a notification. */ data class NotificationMemoryUsage( val packageName: String, + val uid: Int, val notificationKey: String, + val notification: Notification, val objectUsage: NotificationObjectUsage, val viewUsage: List<NotificationViewUsage> ) @@ -34,7 +38,8 @@ data class NotificationObjectUsage( val smallIcon: Int, val largeIcon: Int, val extras: Int, - val style: String?, + /** Style type, integer from [android.stats.sysui.NotificationEnums] */ + val style: Int, val styleIcon: Int, val bigPicture: Int, val extender: Int, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt new file mode 100644 index 000000000000..ffd931c1bde2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt @@ -0,0 +1,173 @@ +/* + * + * 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.statusbar.notification.logging + +import android.stats.sysui.NotificationEnums +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import java.io.PrintWriter +import javax.inject.Inject + +/** Dumps current notification memory use to bug reports for easier debugging. */ +@SysUISingleton +class NotificationMemoryDumper +@Inject +constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable { + + fun init() { + dumpManager.registerNormalDumpable(javaClass.simpleName, this) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + val memoryUse = + NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs) + .sortedWith(compareBy({ it.packageName }, { it.notificationKey })) + dumpNotificationObjects(pw, memoryUse) + dumpNotificationViewUsage(pw, memoryUse) + } + + /** Renders a table of notification object usage into passed [PrintWriter]. */ + private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) { + pw.println("Notification Object Usage") + pw.println("-----------") + pw.println( + "Package".padEnd(35) + + "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom" + ) + pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView") + pw.println() + + memoryUse.forEach { use -> + pw.println( + use.packageName.padEnd(35) + + "\t\t" + + "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" + + (styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) + + "\t\t${use.objectUsage.styleIcon}\t" + + "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" + + "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" + + use.notificationKey + ) + } + + // Calculate totals for easily glanceable summary. + data class Totals( + var smallIcon: Int = 0, + var largeIcon: Int = 0, + var styleIcon: Int = 0, + var bigPicture: Int = 0, + var extender: Int = 0, + var extras: Int = 0, + ) + + val totals = + memoryUse.fold(Totals()) { t, usage -> + t.smallIcon += usage.objectUsage.smallIcon + t.largeIcon += usage.objectUsage.largeIcon + t.styleIcon += usage.objectUsage.styleIcon + t.bigPicture += usage.objectUsage.bigPicture + t.extender += usage.objectUsage.extender + t.extras += usage.objectUsage.extras + t + } + + pw.println() + pw.println("TOTALS") + pw.println( + "".padEnd(35) + + "\t\t" + + "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" + + "".padEnd(15) + + "\t\t${toKb(totals.styleIcon)}\t" + + "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" + + toKb(totals.extras) + ) + pw.println() + } + + /** Renders a table of notification view usage into passed [PrintWriter] */ + private fun dumpNotificationViewUsage( + pw: PrintWriter, + memoryUse: List<NotificationMemoryUsage>, + ) { + + data class Totals( + var smallIcon: Int = 0, + var largeIcon: Int = 0, + var style: Int = 0, + var customViews: Int = 0, + var softwareBitmapsPenalty: Int = 0, + ) + + val totals = Totals() + pw.println("Notification View Usage") + pw.println("-----------") + pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware") + pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps") + pw.println() + memoryUse + .filter { it.viewUsage.isNotEmpty() } + .forEach { use -> + pw.println(use.packageName + " " + use.notificationKey) + use.viewUsage.forEach { view -> + pw.println( + " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" + + "\t${view.largeIcon}\t${view.style}" + + "\t${view.customViews}\t${view.softwareBitmapsPenalty}" + ) + + if (view.viewType == ViewType.TOTAL) { + totals.smallIcon += view.smallIcon + totals.largeIcon += view.largeIcon + totals.style += view.style + totals.customViews += view.customViews + totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty + } + } + } + pw.println() + pw.println("TOTALS") + pw.println( + " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" + + "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" + + "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}" + ) + pw.println() + } + + private fun styleEnumToString(styleEnum: Int): String = + when (styleEnum) { + NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified" + NotificationEnums.STYLE_NONE -> "None" + NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture" + NotificationEnums.STYLE_BIG_TEXT -> "BigText" + NotificationEnums.STYLE_CALL -> "Call" + NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView" + NotificationEnums.STYLE_INBOX -> "Inbox" + NotificationEnums.STYLE_MEDIA -> "Media" + NotificationEnums.STYLE_MESSAGING -> "Messaging" + NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup" + else -> "Unknown" + } + + private fun toKb(bytes: Int): String { + return (bytes / 1024).toString() + " KB" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt new file mode 100644 index 000000000000..ec8501a79fa5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt @@ -0,0 +1,194 @@ +/* + * + * 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.statusbar.notification.logging + +import android.app.StatsManager +import android.util.StatsEvent +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.shared.system.SysUiStatsLog +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.util.traceSection +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.runBlocking + +/** Periodically logs current state of notification memory consumption. */ +@SysUISingleton +class NotificationMemoryLogger +@Inject +constructor( + private val notificationPipeline: NotifPipeline, + private val statsManager: StatsManager, + @Main private val mainDispatcher: CoroutineDispatcher, + @Background private val backgroundExecutor: Executor +) : StatsManager.StatsPullAtomCallback { + + /** + * This class is used to accumulate and aggregate data - the fields mirror values in statd Atom + * with ONE IMPORTANT difference - the values are in bytes, not KB! + */ + internal data class NotificationMemoryUseAtomBuilder(val uid: Int, val style: Int) { + var count: Int = 0 + var countWithInflatedViews: Int = 0 + var smallIconObject: Int = 0 + var smallIconBitmapCount: Int = 0 + var largeIconObject: Int = 0 + var largeIconBitmapCount: Int = 0 + var bigPictureObject: Int = 0 + var bigPictureBitmapCount: Int = 0 + var extras: Int = 0 + var extenders: Int = 0 + var smallIconViews: Int = 0 + var largeIconViews: Int = 0 + var systemIconViews: Int = 0 + var styleViews: Int = 0 + var customViews: Int = 0 + var softwareBitmaps: Int = 0 + var seenCount = 0 + } + + fun init() { + statsManager.setPullAtomCallback( + SysUiStatsLog.NOTIFICATION_MEMORY_USE, + null, + backgroundExecutor, + this + ) + } + + /** Called by statsd to pull data. */ + override fun onPullAtom(atomTag: Int, data: MutableList<StatsEvent>): Int = + traceSection("NML#onPullAtom") { + if (atomTag != SysUiStatsLog.NOTIFICATION_MEMORY_USE) { + return StatsManager.PULL_SKIP + } + + // Notifications can only be retrieved on the main thread, so switch to that thread. + val notifications = getAllNotificationsOnMainThread() + val notificationMemoryUse = + NotificationMemoryMeter.notificationMemoryUse(notifications) + .sortedWith( + compareBy( + { it.packageName }, + { it.objectUsage.style }, + { it.notificationKey } + ) + ) + val usageData = aggregateMemoryUsageData(notificationMemoryUse) + usageData.forEach { (_, use) -> + data.add( + SysUiStatsLog.buildStatsEvent( + SysUiStatsLog.NOTIFICATION_MEMORY_USE, + use.uid, + use.style, + use.count, + use.countWithInflatedViews, + toKb(use.smallIconObject), + use.smallIconBitmapCount, + toKb(use.largeIconObject), + use.largeIconBitmapCount, + toKb(use.bigPictureObject), + use.bigPictureBitmapCount, + toKb(use.extras), + toKb(use.extenders), + toKb(use.smallIconViews), + toKb(use.largeIconViews), + toKb(use.systemIconViews), + toKb(use.styleViews), + toKb(use.customViews), + toKb(use.softwareBitmaps), + use.seenCount + ) + ) + } + + return StatsManager.PULL_SUCCESS + } + + private fun getAllNotificationsOnMainThread() = + runBlocking(mainDispatcher) { + traceSection("NML#getNotifications") { notificationPipeline.allNotifs } + } + + /** Aggregates memory usage data by package and style, returning sums. */ + private fun aggregateMemoryUsageData( + notificationMemoryUse: List<NotificationMemoryUsage> + ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> { + return notificationMemoryUse + .groupingBy { Pair(it.packageName, it.objectUsage.style) } + .aggregate { + _, + accumulator: NotificationMemoryUseAtomBuilder?, + element: NotificationMemoryUsage, + first -> + val use = + if (first) { + NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style) + } else { + accumulator!! + } + + use.count++ + // If the views of the notification weren't inflated, the list of memory usage + // parameters will be empty. + if (element.viewUsage.isNotEmpty()) { + use.countWithInflatedViews++ + } + + use.smallIconObject += element.objectUsage.smallIcon + if (element.objectUsage.smallIcon > 0) { + use.smallIconBitmapCount++ + } + + use.largeIconObject += element.objectUsage.largeIcon + if (element.objectUsage.largeIcon > 0) { + use.largeIconBitmapCount++ + } + + use.bigPictureObject += element.objectUsage.bigPicture + if (element.objectUsage.bigPicture > 0) { + use.bigPictureBitmapCount++ + } + + use.extras += element.objectUsage.extras + use.extenders += element.objectUsage.extender + + // Use totals count which are more accurate when aggregated + // in this manner. + element.viewUsage + .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } + ?.let { + use.smallIconViews += it.smallIcon + use.largeIconViews += it.largeIcon + use.systemIconViews += it.systemIcons + use.styleViews += it.style + use.customViews += it.style + use.softwareBitmaps += it.softwareBitmapsPenalty + } + + return@aggregate use + } + } + + /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ + private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt index 7d39e18ab349..41fb91e3093e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt @@ -1,12 +1,20 @@ package com.android.systemui.statusbar.notification.logging import android.app.Notification +import android.app.Notification.BigPictureStyle +import android.app.Notification.BigTextStyle +import android.app.Notification.CallStyle +import android.app.Notification.DecoratedCustomViewStyle +import android.app.Notification.InboxStyle +import android.app.Notification.MediaStyle +import android.app.Notification.MessagingStyle import android.app.Person import android.graphics.Bitmap import android.graphics.drawable.Icon import android.os.Bundle import android.os.Parcel import android.os.Parcelable +import android.stats.sysui.NotificationEnums import androidx.annotation.WorkerThread import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -19,6 +27,7 @@ internal object NotificationMemoryMeter { private const val TV_EXTENSIONS = "android.tv.EXTENSIONS" private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS" private const val WEARABLE_EXTENSIONS_BACKGROUND = "background" + private const val AUTOGROUP_KEY = "ranker_group" /** Returns a list of memory use entries for currently shown notifications. */ @WorkerThread @@ -29,12 +38,15 @@ internal object NotificationMemoryMeter { .asSequence() .map { entry -> val packageName = entry.sbn.packageName + val uid = entry.sbn.uid val notificationObjectUsage = notificationMemoryUse(entry.sbn.notification, hashSetOf()) val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row) NotificationMemoryUsage( packageName, + uid, NotificationUtils.logKey(entry.sbn.key), + entry.sbn.notification, notificationObjectUsage, notificationViewUsage ) @@ -49,7 +61,9 @@ internal object NotificationMemoryMeter { ): NotificationMemoryUsage { return NotificationMemoryUsage( entry.sbn.packageName, + entry.sbn.uid, NotificationUtils.logKey(entry.sbn.key), + entry.sbn.notification, notificationMemoryUse(entry.sbn.notification, seenBitmaps), NotificationMemoryViewWalker.getViewUsage(entry.row) ) @@ -116,7 +130,13 @@ internal object NotificationMemoryMeter { val wearExtenderBackground = computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps) - val style = notification.notificationStyle + val style = + if (notification.group == AUTOGROUP_KEY) { + NotificationEnums.STYLE_RANKER_GROUP + } else { + styleEnum(notification.notificationStyle) + } + val hasCustomView = notification.contentView != null || notification.bigContentView != null val extrasSize = computeBundleSize(extras) @@ -124,7 +144,7 @@ internal object NotificationMemoryMeter { smallIcon = smallIconUse, largeIcon = largeIconUse, extras = extrasSize, - style = style?.simpleName, + style = style, styleIcon = bigPictureIconUse + peopleUse + @@ -144,6 +164,25 @@ internal object NotificationMemoryMeter { } /** + * Returns logging style enum based on current style class. + * + * @return style value in [NotificationEnums] + */ + private fun styleEnum(style: Class<out Notification.Style>?): Int = + when (style?.name) { + null -> NotificationEnums.STYLE_NONE + BigTextStyle::class.java.name -> NotificationEnums.STYLE_BIG_TEXT + BigPictureStyle::class.java.name -> NotificationEnums.STYLE_BIG_PICTURE + InboxStyle::class.java.name -> NotificationEnums.STYLE_INBOX + MediaStyle::class.java.name -> NotificationEnums.STYLE_MEDIA + DecoratedCustomViewStyle::class.java.name -> + NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW + MessagingStyle::class.java.name -> NotificationEnums.STYLE_MESSAGING + CallStyle::class.java.name -> NotificationEnums.STYLE_CALL + else -> NotificationEnums.STYLE_UNSPECIFIED + } + + /** * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem * bitmaps). Can be slow. */ @@ -176,7 +215,7 @@ internal object NotificationMemoryMeter { * * @return memory usage in bytes or 0 if the icon is Uri/Resource based */ - private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) = + private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>): Int = when (icon?.type) { Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps) Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt index c09cc4306ced..f38c1e557b25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt @@ -18,11 +18,10 @@ package com.android.systemui.statusbar.notification.logging import android.util.Log -import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.notification.collection.NotifPipeline -import java.io.PrintWriter +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import dagger.Lazy import javax.inject.Inject /** This class monitors and logs current Notification memory use. */ @@ -30,9 +29,10 @@ import javax.inject.Inject class NotificationMemoryMonitor @Inject constructor( - val notificationPipeline: NotifPipeline, - val dumpManager: DumpManager, -) : Dumpable { + private val featureFlags: FeatureFlags, + private val notificationMemoryDumper: NotificationMemoryDumper, + private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>, +) { companion object { private const val TAG = "NotificationMemory" @@ -40,127 +40,10 @@ constructor( fun init() { Log.d(TAG, "NotificationMemoryMonitor initialized.") - dumpManager.registerDumpable(javaClass.simpleName, this) - } - - override fun dump(pw: PrintWriter, args: Array<out String>) { - val memoryUse = - NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs) - .sortedWith(compareBy({ it.packageName }, { it.notificationKey })) - dumpNotificationObjects(pw, memoryUse) - dumpNotificationViewUsage(pw, memoryUse) - } - - /** Renders a table of notification object usage into passed [PrintWriter]. */ - private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) { - pw.println("Notification Object Usage") - pw.println("-----------") - pw.println( - "Package".padEnd(35) + - "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom" - ) - pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView") - pw.println() - - memoryUse.forEach { use -> - pw.println( - use.packageName.padEnd(35) + - "\t\t" + - "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" + - (use.objectUsage.style?.take(15) ?: "").padEnd(15) + - "\t\t${use.objectUsage.styleIcon}\t" + - "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" + - "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" + - use.notificationKey - ) + notificationMemoryDumper.init() + if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) { + Log.d(TAG, "Notification memory logging enabled.") + notificationMemoryLogger.get().init() } - - // Calculate totals for easily glanceable summary. - data class Totals( - var smallIcon: Int = 0, - var largeIcon: Int = 0, - var styleIcon: Int = 0, - var bigPicture: Int = 0, - var extender: Int = 0, - var extras: Int = 0, - ) - - val totals = - memoryUse.fold(Totals()) { t, usage -> - t.smallIcon += usage.objectUsage.smallIcon - t.largeIcon += usage.objectUsage.largeIcon - t.styleIcon += usage.objectUsage.styleIcon - t.bigPicture += usage.objectUsage.bigPicture - t.extender += usage.objectUsage.extender - t.extras += usage.objectUsage.extras - t - } - - pw.println() - pw.println("TOTALS") - pw.println( - "".padEnd(35) + - "\t\t" + - "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" + - "".padEnd(15) + - "\t\t${toKb(totals.styleIcon)}\t" + - "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" + - toKb(totals.extras) - ) - pw.println() - } - - /** Renders a table of notification view usage into passed [PrintWriter] */ - private fun dumpNotificationViewUsage( - pw: PrintWriter, - memoryUse: List<NotificationMemoryUsage>, - ) { - - data class Totals( - var smallIcon: Int = 0, - var largeIcon: Int = 0, - var style: Int = 0, - var customViews: Int = 0, - var softwareBitmapsPenalty: Int = 0, - ) - - val totals = Totals() - pw.println("Notification View Usage") - pw.println("-----------") - pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware") - pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps") - pw.println() - memoryUse - .filter { it.viewUsage.isNotEmpty() } - .forEach { use -> - pw.println(use.packageName + " " + use.notificationKey) - use.viewUsage.forEach { view -> - pw.println( - " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" + - "\t${view.largeIcon}\t${view.style}" + - "\t${view.customViews}\t${view.softwareBitmapsPenalty}" - ) - - if (view.viewType == ViewType.TOTAL) { - totals.smallIcon += view.smallIcon - totals.largeIcon += view.largeIcon - totals.style += view.style - totals.customViews += view.customViews - totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty - } - } - } - pw.println() - pw.println("TOTALS") - pw.println( - " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" + - "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" + - "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}" - ) - pw.println() - } - - private fun toKb(bytes: Int): String { - return (bytes / 1024).toString() + " KB" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt index a0bee1502f51..2d042118b3d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt @@ -50,7 +50,11 @@ internal object NotificationMemoryViewWalker { /** * Returns memory usage of public and private views contained in passed - * [ExpandableNotificationRow] + * [ExpandableNotificationRow]. Each entry will correspond to one of the [ViewType] values with + * [ViewType.TOTAL] totalling all memory use. If a type of view is missing, the corresponding + * entry will not appear in resulting list. + * + * This will return an empty list if the ExpandableNotificationRow has no views inflated. */ fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> { if (row == null) { @@ -58,42 +62,72 @@ internal object NotificationMemoryViewWalker { } // The ordering here is significant since it determines deduplication of seen drawables. - return listOf( - getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild), - getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild), - getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild), - getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout), - getTotalUsage(row) - ) + val perViewUsages = + listOf( + getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild), + getViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + row.privateLayout?.contractedChild + ), + getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild), + getViewUsage( + ViewType.PUBLIC_VIEW, + row.publicLayout?.expandedChild, + row.publicLayout?.contractedChild, + row.publicLayout?.headsUpChild + ), + ) + .filterNotNull() + + return if (perViewUsages.isNotEmpty()) { + // Attach summed totals field only if there was any view actually measured. + // This reduces bug report noise and makes checks for collapsed views easier. + val totals = getTotalUsage(row) + if (totals == null) { + perViewUsages + } else { + perViewUsages + totals + } + } else { + listOf() + } } /** * Calculate total usage of all views - we need to do a separate traversal to make sure we don't * double count fields. */ - private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage { - val totalUsage = UsageBuilder() + private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? { val seenObjects = hashSetOf<Int>() - - row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) } - row.privateLayout?.let { child -> - for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) { - (view as? ViewGroup)?.let { v -> - computeViewHierarchyUse(v, totalUsage, seenObjects) - } - } - } - return totalUsage.build(ViewType.TOTAL) + return getViewUsage( + ViewType.TOTAL, + row.privateLayout?.expandedChild, + row.privateLayout?.contractedChild, + row.privateLayout?.headsUpChild, + row.publicLayout?.expandedChild, + row.publicLayout?.contractedChild, + row.publicLayout?.headsUpChild, + seenObjects = seenObjects + ) } private fun getViewUsage( type: ViewType, - rootView: View?, + vararg rootViews: View?, seenObjects: HashSet<Int> = hashSetOf() - ): NotificationViewUsage { - val usageBuilder = UsageBuilder() - (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) } - return usageBuilder.build(type) + ): NotificationViewUsage? { + val usageBuilder = lazy { UsageBuilder() } + rootViews.forEach { rootView -> + (rootView as? ViewGroup)?.let { rootViewGroup -> + computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects) + } + } + + return if (usageBuilder.isInitialized()) { + usageBuilder.value.build(type) + } else { + null + } } private fun computeViewHierarchyUse( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 0213b969551e..20412452b7f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -214,7 +214,11 @@ public abstract class ExpandableOutlineView extends ExpandableView { } else { maxRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); } - mRoundableState = new RoundableState(this, this, maxRadius); + if (mRoundableState == null) { + mRoundableState = new RoundableState(this, this, maxRadius); + } else { + mRoundableState.setMaxRadius(maxRadius); + } setClipToOutline(mAlwaysRoundBothCorners); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index d22bfe8b9e3c..5db95d77d135 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3707,7 +3707,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * of DisplayArea into relative coordinates for all windows, we need to correct the * QS Head bounds here. */ - final int xOffset = Math.round(ev.getRawX() - ev.getX()); + final int xOffset = Math.round(ev.getRawX() - ev.getX() + mQsHeader.getLeft()); final int yOffset = Math.round(ev.getRawY() - ev.getY()); mQsHeaderBound.offsetTo(xOffset, yOffset); return mQsHeaderBound.contains((int) ev.getRawX(), (int) ev.getRawY()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 005cd1bff90c..1fcfe4ef2f88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -35,6 +35,7 @@ import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; +import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; @@ -1147,10 +1148,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void onFoldedStateChangedInternal(boolean isFolded, boolean willGoToSleep) { // Folded state changes are followed by a screen off event. // By default turning off the screen also closes the shade. - // We want to make sure that the shade status is kept after - // folding/unfolding. - boolean isShadeOpen = mShadeController.isShadeOpen(); - boolean leaveOpen = isShadeOpen && !willGoToSleep; + // We want to make sure that the shade status is kept after folding/unfolding. + boolean isShadeOpen = mShadeController.isShadeFullyOpen(); + boolean leaveOpen = isShadeOpen && !willGoToSleep && mState == SHADE; if (DEBUG) { Log.d(TAG, String.format( "#onFoldedStateChanged(): " @@ -1161,18 +1161,17 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { isFolded, willGoToSleep, isShadeOpen, leaveOpen)); } if (leaveOpen) { - if (mKeyguardStateController.isShowing()) { - // When device state changes on keyguard we don't want to keep the state of - // the shade and instead we open clean state of keyguard with shade closed. - // Normally some parts of QS state (like expanded/collapsed) are persisted and - // that causes incorrect UI rendering, especially when changing state with QS - // expanded. To prevent that we can close QS which resets QS and some parts of - // the shade to its default state. Read more in b/201537421 - mCloseQsBeforeScreenOff = true; - } else { - // below makes shade stay open when going from folded to unfolded - mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - } + // below makes shade stay open when going from folded to unfolded + mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + } + if (mState != SHADE && isShadeOpen) { + // When device state changes on KEYGUARD/SHADE_LOCKED we don't want to keep the state of + // the shade and instead we open clean state of keyguard with shade closed. + // Normally some parts of QS state (like expanded/collapsed) are persisted and + // that causes incorrect UI rendering, especially when changing state with QS + // expanded. To prevent that we can close QS which resets QS and some parts of + // the shade to its default state. Read more in b/201537421 + mCloseQsBeforeScreenOff = true; } } @@ -3574,7 +3573,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override - public void onScreenTurningOn(Runnable onDrawn) { + public void onScreenTurningOn() { mFalsingCollector.onScreenTurningOn(); mNotificationPanelViewController.onScreenTurningOn(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 05bf8604c2c0..be6e0cc8a996 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -50,6 +50,8 @@ import com.android.systemui.ActivityIntentHelper; import com.android.systemui.EventLogTags; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; @@ -106,6 +108,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private final LockPatternUtils mLockPatternUtils; private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback; private final ActivityIntentHelper mActivityIntentHelper; + private final FeatureFlags mFeatureFlags; private final MetricsLogger mMetricsLogger; private final StatusBarNotificationActivityStarterLogger mLogger; @@ -149,7 +152,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte NotificationPanelViewController panel, ActivityLaunchAnimator activityLaunchAnimator, NotificationLaunchAnimatorControllerProvider notificationAnimationProvider, - LaunchFullScreenIntentProvider launchFullScreenIntentProvider) { + LaunchFullScreenIntentProvider launchFullScreenIntentProvider, + FeatureFlags featureFlags) { mContext = context; mMainThreadHandler = mainThreadHandler; mUiBgExecutor = uiBgExecutor; @@ -170,6 +174,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mLockPatternUtils = lockPatternUtils; mStatusBarRemoteInputCallback = remoteInputCallback; mActivityIntentHelper = activityIntentHelper; + mFeatureFlags = featureFlags; mMetricsLogger = metricsLogger; mLogger = logger; mOnUserInteractionCallback = onUserInteractionCallback; @@ -548,7 +553,10 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mLogger.logFullScreenIntentSuppressedByVR(entry); return; } - + if (mFeatureFlags.isEnabled(Flags.FSI_CHROME)) { + // FsiChromeRepo runs its own implementation of launchFullScreenIntent + return; + } // Stop screensaver if the notification has a fullscreen intent. // (like an incoming phone call) mUiBgExecutor.execute(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index 6216acd6081e..101bd4483cb3 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -16,6 +16,7 @@ package com.android.systemui.unfold +import android.annotation.BinderThread import android.content.Context import android.hardware.devicestate.DeviceStateManager import android.os.PowerManager @@ -41,7 +42,6 @@ import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch /** @@ -52,7 +52,7 @@ import kotlinx.coroutines.launch class FoldAodAnimationController @Inject constructor( - @Main private val executor: DelayableExecutor, + @Main private val mainExecutor: DelayableExecutor, private val context: Context, private val deviceStateManager: DeviceStateManager, private val wakefulnessLifecycle: WakefulnessLifecycle, @@ -89,7 +89,7 @@ constructor( override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) { this.centralSurfaces = centralSurfaces - deviceStateManager.registerCallback(executor, FoldListener()) + deviceStateManager.registerCallback(mainExecutor, FoldListener()) wakefulnessLifecycle.addObserver(this) // TODO(b/254878364): remove this call to NPVC.getView() @@ -139,7 +139,8 @@ constructor( * @param onReady callback when the animation is ready * @see [com.android.systemui.keyguard.KeyguardViewMediator] */ - fun onScreenTurningOn(onReady: Runnable) { + @BinderThread + fun onScreenTurningOn(onReady: Runnable) = mainExecutor.execute { if (shouldPlayAnimation) { // The device was not dozing and going to sleep after folding, play the animation @@ -179,12 +180,13 @@ constructor( } } - fun onScreenTurnedOn() { + @BinderThread + fun onScreenTurnedOn() = mainExecutor.execute { if (shouldPlayAnimation) { cancelAnimation?.run() // Post starting the animation to the next frame to avoid junk due to inset changes - cancelAnimation = executor.executeDelayed(startAnimationRunnable, /* delayMillis= */ 0) + cancelAnimation = mainExecutor.executeDelayed(startAnimationRunnable, /* delayMillis= */ 0) shouldPlayAnimation = false } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index b2ec27c8ce43..9cca95028729 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.unfold +import android.annotation.BinderThread import android.content.ContentResolver import android.content.Context import android.graphics.PixelFormat @@ -34,9 +35,7 @@ import android.view.SurfaceControlViewHost import android.view.SurfaceSession import android.view.WindowManager import android.view.WindowlessWindowManager -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LinearLightRevealEffect @@ -45,7 +44,7 @@ import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayR import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled -import com.android.systemui.util.Assert.isMainThread +import com.android.systemui.util.concurrency.ThreadFactory import com.android.systemui.util.traceSection import com.android.wm.shell.displayareahelper.DisplayAreaHelper import java.util.Optional @@ -64,14 +63,16 @@ constructor( private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, private val displayAreaHelper: Optional<DisplayAreaHelper>, @Main private val executor: Executor, - @UiBackground private val backgroundExecutor: Executor, - @Background private val bgHandler: Handler, + private val threadFactory: ThreadFactory, private val rotationChangeProvider: RotationChangeProvider, ) { private val transitionListener = TransitionListener() private val rotationWatcher = RotationWatcher() + private lateinit var bgHandler: Handler + private lateinit var bgExecutor: Executor + private lateinit var wwm: WindowlessWindowManager private lateinit var unfoldedDisplayInfo: DisplayInfo private lateinit var overlayContainer: SurfaceControl @@ -84,7 +85,12 @@ constructor( private var currentRotation: Int = context.display!!.rotation fun init() { - deviceStateManager.registerCallback(executor, FoldListener()) + // This method will be called only on devices where this animation is enabled, + // so normally this thread won't be created + bgHandler = threadFactory.buildHandlerOnNewThread(TAG) + bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler) + + deviceStateManager.registerCallback(bgExecutor, FoldListener()) unfoldTransitionProgressProvider.addCallback(transitionListener) rotationChangeProvider.addCallback(rotationWatcher) @@ -122,20 +128,23 @@ constructor( * @param onOverlayReady callback when the overlay is drawn and visible on the screen * @see [com.android.systemui.keyguard.KeyguardViewMediator] */ + @BinderThread fun onScreenTurningOn(onOverlayReady: Runnable) { - Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn") - try { - // Add the view only if we are unfolding and this is the first screen on - if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) { - executeInBackground { addOverlay(onOverlayReady, reason = UNFOLD) } - isUnfoldHandled = true - } else { - // No unfold transition, immediately report that overlay is ready - executeInBackground { ensureOverlayRemoved() } - onOverlayReady.run() + executeInBackground { + Trace.beginSection("$TAG#onScreenTurningOn") + try { + // Add the view only if we are unfolding and this is the first screen on + if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) { + addOverlay(onOverlayReady, reason = UNFOLD) + isUnfoldHandled = true + } else { + // No unfold transition, immediately report that overlay is ready + ensureOverlayRemoved() + onOverlayReady.run() + } + } finally { + Trace.endSection() } - } finally { - Trace.endSection() } } @@ -154,17 +163,18 @@ constructor( LightRevealScrim(context, null).apply { revealEffect = createLightRevealEffect() isScrimOpaqueChangedListener = Consumer {} - revealAmount = when (reason) { - FOLD -> TRANSPARENT - UNFOLD -> BLACK - } + revealAmount = + when (reason) { + FOLD -> TRANSPARENT + UNFOLD -> BLACK + } } val params = getLayoutParams() newRoot.setView(newView, params) if (onOverlayReady != null) { - Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) + Trace.beginAsyncSection("$TAG#relayout", 0) newRoot.relayout(params) { transaction -> val vsyncId = Choreographer.getSfInstance().vsyncId @@ -179,8 +189,8 @@ constructor( transaction .setFrameTimelineVsync(vsyncId + 1) - .addTransactionCommittedListener(backgroundExecutor) { - Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) + .addTransactionCommittedListener(bgExecutor) { + Trace.endAsyncSection("$TAG#relayout", 0) onOverlayReady.run() } .apply() @@ -233,7 +243,8 @@ constructor( } private fun getUnfoldedDisplayInfo(): DisplayInfo = - displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED) + displayManager + .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED) .asSequence() .map { DisplayInfo().apply { it.getDisplayInfo(this) } } .filter { it.type == Display.TYPE_INTERNAL } @@ -261,10 +272,10 @@ constructor( private inner class RotationWatcher : RotationChangeProvider.RotationListener { override fun onRotationChanged(newRotation: Int) { - traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") { - if (currentRotation != newRotation) { - currentRotation = newRotation - executeInBackground { + executeInBackground { + traceSection("$TAG#onRotationChanged") { + if (currentRotation != newRotation) { + currentRotation = newRotation scrimView?.revealEffect = createLightRevealEffect() root?.relayout(getLayoutParams()) } @@ -274,7 +285,10 @@ constructor( } private fun executeInBackground(f: () -> Unit) { - ensureInMainThread() + check(Looper.myLooper() != bgHandler.looper) { + "Trying to execute using background handler while already running" + + " in the background handler" + } // The UiBackground executor is not used as it doesn't have a prepared looper. bgHandler.post(f) } @@ -283,25 +297,25 @@ constructor( check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" } } - private fun ensureInMainThread() { - isMainThread() - } - private inner class FoldListener : FoldStateListener( context, Consumer { isFolded -> if (isFolded) { - executeInBackground { ensureOverlayRemoved() } + ensureOverlayRemoved() isUnfoldHandled = false } this.isFolded = isFolded } ) - private enum class AddOverlayReason { FOLD, UNFOLD } + private enum class AddOverlayReason { + FOLD, + UNFOLD + } private companion object { + const val TAG = "UnfoldLightRevealOverlayAnimation" const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE // Put the unfold overlay below the rotation animation screenshot to hide the moment diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 7da2d47c1226..52b7fb63c1a2 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -39,9 +39,9 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.user_switcher_fullscreen) - window.decorView.getWindowInsetsController().apply { - setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) - hide(Type.systemBars()) + window.decorView.windowInsetsController?.let { controller -> + controller.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + controller.hide(Type.systemBars()) } val viewModel = ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java] diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt index 6cd384f17803..ceebcb77fde2 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt @@ -25,8 +25,11 @@ import java.util.concurrent.atomic.AtomicReference */ class PendingTasksContainer { - private var pendingTasksCount: AtomicInteger = AtomicInteger(0) - private var completionCallback: AtomicReference<Runnable> = AtomicReference() + @Volatile + private var pendingTasksCount = AtomicInteger(0) + + @Volatile + private var completionCallback = AtomicReference<Runnable>() /** * Registers a task that we should wait for diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt deleted file mode 100644 index fa8c8982bccb..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class KeyguardListenQueueTest : SysuiTestCase() { - - @Test - fun testQueueIsBounded() { - val size = 5 - val queue = KeyguardListenQueue(sizePerModality = size) - - val fingerprints = List(100) { fingerprintModel(it) } - fingerprints.forEach { queue.add(it) } - - assertThat(queue.models).containsExactlyElementsIn(fingerprints.takeLast(size)) - - val faces = List(100) { faceModel(it) } - faces.forEach { queue.add(it) } - - assertThat(queue.models).containsExactlyElementsIn( - faces.takeLast(size) + fingerprints.takeLast(5) - ) - - repeat(100) { - queue.add(faceModel(-1)) - queue.add(fingerprintModel(-1)) - } - assertThat(queue.models).hasSize(2 * size) - assertThat(queue.models.count { it.userId == -1 }).isEqualTo(2 * size) - } -} - -private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel( - timeMillis = System.currentTimeMillis(), - userId = user, - listening = false, - biometricEnabledForUser = false, - bouncerIsOrWillShow = false, - canSkipBouncer = false, - credentialAttempted = false, - deviceInteractive = false, - dreaming = false, - fingerprintDisabled = false, - fingerprintLockedOut = false, - goingToSleep = false, - keyguardGoingAway = false, - keyguardIsVisible = false, - keyguardOccluded = false, - occludingAppRequestingFp = false, - primaryUser = false, - shouldListenSfpsState = false, - shouldListenForFingerprintAssistant = false, - strongerAuthRequired = false, - switchingUser = false, - udfps = false, - userDoesNotHaveTrust = false -) - -private fun faceModel(user: Int) = KeyguardFaceListenModel( - timeMillis = System.currentTimeMillis(), - userId = user, - listening = false, - authInterruptActive = false, - biometricSettingEnabledForUser = false, - bouncerFullyShown = false, - faceAndFpNotAuthenticated = false, - faceAuthAllowed = true, - faceDisabled = false, - faceLockedOut = false, - goingToSleep = false, - keyguardAwake = false, - keyguardGoingAway = false, - listeningForFaceAssistant = false, - occludingAppRequestingFaceAuth = false, - primaryUser = false, - secureCameraLaunched = false, - supportsDetect = true, - switchingUser = false, - udfpsBouncerShowing = false, - udfpsFingerDown = false, - userNotTrustedOrDetectionIsNeeded = false -) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2aef726f2221..13cd328d00e0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1424,7 +1424,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN the showTrustGrantedMessage should be called with the first message verify(mTestCallback).onTrustGrantedForCurrentUser( - anyBoolean(), + anyBoolean() /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, eq(new TrustGrantFlags(0)), eq("Unlocked by wearable")); } @@ -1878,6 +1879,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN onTrustGrantedForCurrentUser callback called verify(callback).onTrustGrantedForCurrentUser( eq(true) /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)), eq(null) /* message */ ); @@ -1902,6 +1904,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN onTrustGrantedForCurrentUser callback called verify(callback).onTrustGrantedForCurrentUser( eq(false) /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)), eq(null) /* message */ ); @@ -1927,6 +1930,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN onTrustGrantedForCurrentUser callback called verify(callback, never()).onTrustGrantedForCurrentUser( anyBoolean() /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, anyObject() /* flags */, anyString() /* message */ ); @@ -1953,6 +1957,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN onTrustGrantedForCurrentUser callback called verify(callback).onTrustGrantedForCurrentUser( eq(true) /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)), eq(null) /* message */ @@ -1980,6 +1985,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN onTrustGrantedForCurrentUser callback called verify(callback, never()).onTrustGrantedForCurrentUser( eq(true) /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)), anyString() /* message */ ); @@ -2006,6 +2012,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN onTrustGrantedForCurrentUser callback called verify(callback, never()).onTrustGrantedForCurrentUser( eq(true) /* dismissKeyguard */, + eq(true) /* newlyUnlocked */, eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)), anyString() /* message */ diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt index 5734c3de70e0..34e78eb8c2eb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt @@ -52,8 +52,6 @@ class ScreenOnCoordinatorTest : SysuiTestCase() { private lateinit var foldAodAnimationController: FoldAodAnimationController @Mock private lateinit var unfoldAnimation: UnfoldLightRevealOverlayAnimation - @Mock - private lateinit var screenLifecycle: ScreenLifecycle @Captor private lateinit var readyCaptor: ArgumentCaptor<Runnable> @@ -69,13 +67,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() { .thenReturn(foldAodAnimationController) screenOnCoordinator = ScreenOnCoordinator( - screenLifecycle, Optional.of(unfoldComponent), - FakeExecution() ) - - // Make sure screen events are registered to observe - verify(screenLifecycle).addObserver(screenOnCoordinator) } @Test @@ -93,9 +86,7 @@ class ScreenOnCoordinatorTest : SysuiTestCase() { fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() { // Recreate with empty unfoldComponent screenOnCoordinator = ScreenOnCoordinator( - screenLifecycle, Optional.empty(), - FakeExecution() ) screenOnCoordinator.onScreenTurningOn(runnable) @@ -105,11 +96,11 @@ class ScreenOnCoordinatorTest : SysuiTestCase() { private fun onUnfoldOverlayReady() { verify(unfoldAnimation).onScreenTurningOn(capture(readyCaptor)) - readyCaptor.getValue().run() + readyCaptor.value.run() } private fun onFoldAodReady() { verify(foldAodAnimationController).onScreenTurningOn(capture(readyCaptor)) - readyCaptor.getValue().run() + readyCaptor.value.run() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt index 09c8e6ac1268..26a63e22eb8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt @@ -167,6 +167,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { featureFlags = FakeFeatureFlags().apply { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) + set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) }, repository = { quickAffordanceRepository }, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java index f46d58d679b5..70a0415d2e35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java @@ -58,15 +58,15 @@ public class ScreenLifecycleTest extends SysuiTestCase { @Test public void screenTurningOn() throws Exception { Runnable onDrawn = () -> {}; - mScreen.dispatchScreenTurningOn(onDrawn); + mScreen.dispatchScreenTurningOn(); assertEquals(ScreenLifecycle.SCREEN_TURNING_ON, mScreen.getScreenState()); - verify(mScreenObserverMock).onScreenTurningOn(onDrawn); + verify(mScreenObserverMock).onScreenTurningOn(); } @Test public void screenTurnedOn() throws Exception { - mScreen.dispatchScreenTurningOn(null); + mScreen.dispatchScreenTurningOn(); mScreen.dispatchScreenTurnedOn(); assertEquals(ScreenLifecycle.SCREEN_ON, mScreen.getScreenState()); @@ -75,7 +75,7 @@ public class ScreenLifecycleTest extends SysuiTestCase { @Test public void screenTurningOff() throws Exception { - mScreen.dispatchScreenTurningOn(null); + mScreen.dispatchScreenTurningOn(); mScreen.dispatchScreenTurnedOn(); mScreen.dispatchScreenTurningOff(); @@ -85,7 +85,7 @@ public class ScreenLifecycleTest extends SysuiTestCase { @Test public void screenTurnedOff() throws Exception { - mScreen.dispatchScreenTurningOn(null); + mScreen.dispatchScreenTurningOn(); mScreen.dispatchScreenTurnedOn(); mScreen.dispatchScreenTurningOff(); mScreen.dispatchScreenTurnedOff(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index ce9c1da422f5..5d2f0eb01de1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -16,13 +16,10 @@ package com.android.systemui.keyguard.data.repository -import android.animation.AnimationHandler.AnimationFrameCallbackProvider import android.animation.ValueAnimator import android.util.Log import android.util.Log.TerribleFailure import android.util.Log.TerribleFailureHandler -import android.view.Choreographer.FrameCallback -import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators @@ -32,22 +29,17 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.util.KeyguardTransitionRunner import com.google.common.truth.Truth.assertThat import java.math.BigDecimal import java.math.RoundingMode import java.util.UUID -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.After -import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -60,12 +52,14 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { private lateinit var underTest: KeyguardTransitionRepository private lateinit var oldWtfHandler: TerribleFailureHandler private lateinit var wtfHandler: WtfHandler + private lateinit var runner: KeyguardTransitionRunner @Before fun setUp() { underTest = KeyguardTransitionRepositoryImpl() wtfHandler = WtfHandler() oldWtfHandler = Log.setWtfHandler(wtfHandler) + runner = KeyguardTransitionRunner(underTest) } @After @@ -75,56 +69,37 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { @Test fun `startTransition runs animator to completion`() = - runBlocking(IMMEDIATE) { - val (animator, provider) = setupAnimator(this) - + TestScope().runTest { val steps = mutableListOf<TransitionStep>() val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) - underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) - - val startTime = System.currentTimeMillis() - while (animator.isRunning()) { - yield() - if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { - fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") - } - } + runner.startTransition( + this, + TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()), + maxFrames = 100 + ) assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN) - job.cancel() - provider.stop() } @Test - @FlakyTest(bugId = 260213291) - fun `starting second transition will cancel the first transition`() { - runBlocking(IMMEDIATE) { - val (animator, provider) = setupAnimator(this) - + fun `starting second transition will cancel the first transition`() = + TestScope().runTest { val steps = mutableListOf<TransitionStep>() val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) - - underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) - // 3 yields(), alternating with the animator, results in a value 0.1, which can be - // canceled and tested against - yield() - yield() - yield() + runner.startTransition( + this, + TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()), + maxFrames = 3, + ) // Now start 2nd transition, which will interrupt the first val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this) - val (animator2, provider2) = setupAnimator(this) - underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2)) - - val startTime = System.currentTimeMillis() - while (animator2.isRunning()) { - yield() - if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { - fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") - } - } + runner.startTransition( + this, + TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()), + ) val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1)) assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN) @@ -134,31 +109,25 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { job.cancel() job2.cancel() - provider.stop() - provider2.stop() } - } @Test fun `Null animator enables manual control with updateTransition`() = - runBlocking(IMMEDIATE) { + TestScope().runTest { val steps = mutableListOf<TransitionStep>() val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) val uuid = underTest.startTransition( - TransitionInfo( - ownerName = OWNER_NAME, - from = AOD, - to = LOCKSCREEN, - animator = null, - ) + TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null) ) + runCurrent() checkNotNull(uuid).let { underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) underTest.updateTransition(it, 1f, TransitionState.FINISHED) } + runCurrent() assertThat(steps.size).isEqualTo(3) assertThat(steps[0]) @@ -256,57 +225,11 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isFalse() } - private fun setupAnimator( - scope: CoroutineScope - ): Pair<ValueAnimator, TestFrameCallbackProvider> { - val animator = - ValueAnimator().apply { - setInterpolator(Interpolators.LINEAR) - setDuration(ANIMATION_DURATION) - } - - val provider = TestFrameCallbackProvider(animator, scope) - provider.start() - - return Pair(animator, provider) - } - - /** Gives direct control over ValueAnimator. See [AnimationHandler] */ - private class TestFrameCallbackProvider( - private val animator: ValueAnimator, - private val scope: CoroutineScope, - ) : AnimationFrameCallbackProvider { - - private var frameCount = 1L - private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null)) - private var job: Job? = null - - fun start() { - animator.getAnimationHandler().setProvider(this) - - job = - scope.launch { - frames.collect { - // Delay is required for AnimationHandler to properly register a callback - yield() - val (frameNumber, callback) = it - callback?.doFrame(frameNumber) - } - } - } - - fun stop() { - job?.cancel() - animator.getAnimationHandler().setProvider(null) - } - - override fun postFrameCallback(cb: FrameCallback) { - frames.value = Pair(frameCount++, cb) + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(10) } - override fun postCommitCallback(runnable: Runnable) {} - override fun getFrameTime() = frameCount - override fun getFrameDelay() = 1L - override fun setFrameDelay(delay: Long) {} } private class WtfHandler : TerribleFailureHandler { @@ -317,9 +240,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } companion object { - private const val MAX_TEST_DURATION = 100L - private const val ANIMATION_DURATION = 10L - private const val OWNER_NAME = "Test" - private val IMMEDIATE = Dispatchers.Main.immediate + private const val OWNER_NAME = "KeyguardTransitionRunner" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt new file mode 100644 index 000000000000..a6cf84053861 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -0,0 +1,140 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.keyguard.util.KeyguardTransitionRunner +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** + * Class for testing user journeys through the interactors. They will all be activated during setup, + * to ensure the expected transitions are still triggered. + */ +@SmallTest +@RunWith(JUnit4::class) +class KeyguardTransitionScenariosTest : SysuiTestCase() { + private lateinit var testScope: TestScope + + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var shadeRepository: ShadeRepository + + // Used to issue real transition steps for test input + private lateinit var runner: KeyguardTransitionRunner + private lateinit var transitionRepository: KeyguardTransitionRepository + + // Used to verify transition requests for test output + @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository + + private lateinit var lockscreenBouncerTransitionInteractor: + LockscreenBouncerTransitionInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + + keyguardRepository = FakeKeyguardRepository() + shadeRepository = FakeShadeRepository() + + /* Used to issue full transition steps, to better simulate a real device */ + transitionRepository = KeyguardTransitionRepositoryImpl() + runner = KeyguardTransitionRunner(transitionRepository) + + lockscreenBouncerTransitionInteractor = + LockscreenBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = KeyguardInteractor(keyguardRepository), + shadeRepository = shadeRepository, + keyguardTransitionRepository = mockTransitionRepository, + keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), + ) + lockscreenBouncerTransitionInteractor.start() + } + + @Test + fun `LOCKSCREEN to BOUNCER via bouncer showing call`() = + testScope.runTest { + // GIVEN a device that has at least woken up + keyguardRepository.setWakefulnessModel(startingToWake()) + runCurrent() + + // GIVEN a transition has run to LOCKSCREEN + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.OFF, + to = KeyguardState.LOCKSCREEN, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + + // WHEN the bouncer is set to show + keyguardRepository.setBouncerShowing(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to BOUNCER should occur + assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.BOUNCER) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + private fun startingToWake() = + WakefulnessModel( + WakefulnessState.STARTING_TO_WAKE, + true, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt new file mode 100644 index 000000000000..c88f84a028ed --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt @@ -0,0 +1,101 @@ +/* + * 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.keyguard.util + +import android.animation.AnimationHandler.AnimationFrameCallbackProvider +import android.animation.ValueAnimator +import android.view.Choreographer.FrameCallback +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.TransitionInfo +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.junit.Assert.fail + +/** + * Gives direct control over ValueAnimator, in order to make transition tests deterministic. See + * [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly. + */ +class KeyguardTransitionRunner( + val repository: KeyguardTransitionRepository, +) : AnimationFrameCallbackProvider { + + private var frameCount = 1L + private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null)) + private var job: Job? = null + private var isTerminated = false + + /** + * For transitions being directed by an animator. Will control the number of frames being + * generated so the values are deterministic. + */ + suspend fun startTransition(scope: CoroutineScope, info: TransitionInfo, maxFrames: Int = 100) { + // AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main + // thread + withContext(Dispatchers.Main) { + info.animator!!.getAnimationHandler().setProvider(this@KeyguardTransitionRunner) + } + + job = + scope.launch { + frames.collect { + val (frameNumber, callback) = it + + isTerminated = frameNumber >= maxFrames + if (!isTerminated) { + withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) } + } + } + } + withContext(Dispatchers.Main) { repository.startTransition(info) } + + waitUntilComplete(info.animator!!) + } + + suspend private fun waitUntilComplete(animator: ValueAnimator) { + withContext(Dispatchers.Main) { + val startTime = System.currentTimeMillis() + while (!isTerminated && animator.isRunning()) { + delay(1) + if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { + fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") + } + } + + animator.getAnimationHandler().setProvider(null) + } + + job?.cancel() + } + + override fun postFrameCallback(cb: FrameCallback) { + frames.value = Pair(frameCount++, cb) + } + override fun postCommitCallback(runnable: Runnable) {} + override fun getFrameTime() = frameCount + override fun getFrameDelay() = 1L + override fun setFrameDelay(delay: Long) {} + + companion object { + private const val MAX_TEST_DURATION = 100L + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS new file mode 100644 index 000000000000..7ccb316dbca5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS @@ -0,0 +1,8 @@ +# Bug component: 1254381 +azappone@google.com +achalke@google.com +juliacr@google.com +madym@google.com +mgalhardo@google.com +petrcermak@google.com +vanjan@google.com
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index caf8321949ca..5058373e39b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -226,7 +226,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { + " " + mockTileViewString + "\n" + " media bounds: null\n" + " horizontal layout: false\n" - + " last orientation: 0\n"; + + " last orientation: 0\n" + + " mShouldUseSplitNotificationShade: false\n"; assertEquals(expected, w.getBuffer().toString()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 5e082f686ea3..6cf642cb7fd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -135,10 +135,10 @@ class QSPanelControllerTest : SysuiTestCase() { fun configurationChange_onlySplitShadeConfigChanges_tileAreRedistributed() { testableResources.addOverride(R.bool.config_use_split_notification_shade, false) controller.mOnConfigurationChangedListener.onConfigurationChange(configuration) - verify(pagedTileLayout, never()).forceTilesRedistribution() + verify(pagedTileLayout, never()).forceTilesRedistribution(any()) testableResources.addOverride(R.bool.config_use_split_notification_shade, true) controller.mOnConfigurationChangedListener.onConfigurationChange(configuration) - verify(pagedTileLayout).forceTilesRedistribution() + verify(pagedTileLayout).forceTilesRedistribution("Split shade state changed") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index 7c930b196d68..d52b29642acf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl import com.android.systemui.qs.tileimpl.QSTileViewImpl import com.google.common.truth.Truth.assertThat @@ -34,6 +35,7 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -42,6 +44,9 @@ import org.mockito.MockitoAnnotations @RunWithLooper @SmallTest class QSPanelTest : SysuiTestCase() { + + @Mock private lateinit var qsLogger: QSLogger + private lateinit var testableLooper: TestableLooper private lateinit var qsPanel: QSPanel @@ -57,7 +62,7 @@ class QSPanelTest : SysuiTestCase() { qsPanel = QSPanel(context, null) qsPanel.mUsingMediaPlayer = true - qsPanel.initialize() + qsPanel.initialize(qsLogger) // QSPanel inflates a footer inside of it, mocking it here footer = LinearLayout(context).apply { id = R.id.qs_footer } qsPanel.addView(footer, MATCH_PARENT, 100) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt index a6a584d2e622..3fba3938db19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt @@ -7,10 +7,12 @@ import android.view.accessibility.AccessibilityNodeInfo import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.logging.QSLogger import com.google.common.truth.Truth import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations @@ -19,6 +21,8 @@ import org.mockito.MockitoAnnotations @SmallTest class QuickQSPanelTest : SysuiTestCase() { + @Mock private lateinit var qsLogger: QSLogger + private lateinit var testableLooper: TestableLooper private lateinit var quickQSPanel: QuickQSPanel @@ -32,7 +36,7 @@ class QuickQSPanelTest : SysuiTestCase() { testableLooper.runWithLooper { quickQSPanel = QuickQSPanel(mContext, null) - quickQSPanel.initialize() + quickQSPanel.initialize(qsLogger) quickQSPanel.onFinishInflate() // Provides a parent with non-zero size for QSPanel diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 351274913323..d629d8b61ad4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -1681,6 +1681,42 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { assertThat(mNotificationPanelViewController.isFullyExpanded()).isTrue(); } + @Test + public void shadeExpanded_inShadeState() { + mStatusBarStateController.setState(SHADE); + + mNotificationPanelViewController.setExpandedHeight(0); + assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isFalse(); + + int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); + mNotificationPanelViewController.setExpandedHeight(transitionDistance); + assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue(); + } + + @Test + public void shadeExpanded_onKeyguard() { + mStatusBarStateController.setState(KEYGUARD); + + int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); + mNotificationPanelViewController.setExpandedHeight(transitionDistance); + assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isFalse(); + + // set maxQsExpansion in NPVC + int maxQsExpansion = 123; + mNotificationPanelViewController.setQs(mQs); + when(mQs.getDesiredHeight()).thenReturn(maxQsExpansion); + triggerLayoutChange(); + + mNotificationPanelViewController.setQsExpansionHeight(maxQsExpansion); + assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue(); + } + + @Test + public void shadeExpanded_onShadeLocked() { + mStatusBarStateController.setState(SHADE_LOCKED); + assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue(); + } + private static MotionEvent createMotionEvent(int x, int y, int action) { return MotionEvent.obtain( /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index c280ec8c4ec8..8d96932f0051 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -1042,7 +1042,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // GIVEN a trust granted message but trust isn't granted final String trustGrantedMsg = "testing trust granted message"; mController.getKeyguardCallback().onTrustGrantedForCurrentUser( - false, new TrustGrantFlags(0), trustGrantedMsg); + false, false, new TrustGrantFlags(0), trustGrantedMsg); verifyHideIndication(INDICATION_TYPE_TRUST); @@ -1067,7 +1067,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN the showTrustGranted method is called final String trustGrantedMsg = "testing trust granted message"; mController.getKeyguardCallback().onTrustGrantedForCurrentUser( - false, new TrustGrantFlags(0), trustGrantedMsg); + false, false, new TrustGrantFlags(0), trustGrantedMsg); // THEN verify the trust granted message shows verifyIndicationMessage( @@ -1085,7 +1085,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN the showTrustGranted method is called with a null message mController.getKeyguardCallback().onTrustGrantedForCurrentUser( - false, new TrustGrantFlags(0), null); + false, false, new TrustGrantFlags(0), null); // THEN verify the default trust granted message shows verifyIndicationMessage( @@ -1103,7 +1103,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN the showTrustGranted method is called with an EMPTY string mController.getKeyguardCallback().onTrustGrantedForCurrentUser( - false, new TrustGrantFlags(0), ""); + false, false, new TrustGrantFlags(0), ""); // THEN verify NO trust message is shown verifyNoMessage(INDICATION_TYPE_TRUST); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt new file mode 100644 index 000000000000..a6a9e51aa555 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt @@ -0,0 +1,210 @@ +/* + * 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.statusbar.notification.fsi + +import android.R +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.service.dreams.IDreamManager +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.concurrent.Executor +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +class FsiChromeRepoTest : SysuiTestCase() { + + @Mock lateinit var centralSurfaces: CentralSurfaces + @Mock lateinit var fsiChromeRepo: FsiChromeRepo + @Mock lateinit var packageManager: PackageManager + + var keyguardRepo = FakeKeyguardRepository() + @Mock private lateinit var applicationInfo: ApplicationInfo + + @Mock lateinit var launchFullScreenIntentProvider: LaunchFullScreenIntentProvider + var featureFlags = FakeFeatureFlags() + @Mock lateinit var dreamManager: IDreamManager + + // Execute all foreground & background requests immediately + private val uiBgExecutor = Executor { r -> r.run() } + + private val appName: String = "appName" + private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android) + private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // Set up package manager mocks + whenever(packageManager.getApplicationIcon(anyString())).thenReturn(appIcon) + whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) + .thenReturn(appIcon) + whenever(packageManager.getApplicationLabel(any())).thenReturn(appName) + mContext.setMockPackageManager(packageManager) + + fsiChromeRepo = + FsiChromeRepo( + mContext, + packageManager, + keyguardRepo, + launchFullScreenIntentProvider, + featureFlags, + uiBgExecutor, + dreamManager, + centralSurfaces + ) + } + + private fun createFsiEntry(fsi: PendingIntent): NotificationEntry { + val nb = + Notification.Builder(mContext, "a") + .setContentTitle("foo") + .setSmallIcon(R.drawable.sym_def_app_icon) + .setFullScreenIntent(fsi, /* highPriority= */ true) + + val sbn = + StatusBarNotification( + "pkg", + "opPkg", + /* id= */ 0, + "tag" + System.currentTimeMillis(), + /* uid= */ 0, + /* initialPid */ 0, + nb.build(), + UserHandle(0), + /* overrideGroupKey= */ null, + /* postTime= */ 0 + ) + + val entry = Mockito.mock(NotificationEntry::class.java) + whenever(entry.importance).thenReturn(NotificationManager.IMPORTANCE_HIGH) + whenever(entry.sbn).thenReturn(sbn) + return entry + } + + @Test + fun testLaunchFullscreenIntent_flagNotEnabled_noLaunch() { + // Setup + featureFlags.set(Flags.FSI_CHROME, false) + + // Test + val entry = createFsiEntry(fsi) + fsiChromeRepo.launchFullscreenIntent(entry) + + // Verify + Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent() + } + + @Test + fun testLaunchFullscreenIntent_notOnKeyguard_noLaunch() { + // Setup + featureFlags.set(Flags.FSI_CHROME, true) + keyguardRepo.setKeyguardShowing(false) + + // Test + val entry = createFsiEntry(fsi) + fsiChromeRepo.launchFullscreenIntent(entry) + + // Verify + Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent() + } + + @Test + fun testLaunchFullscreenIntent_stopsScreensaver() { + // Setup + featureFlags.set(Flags.FSI_CHROME, true) + keyguardRepo.setKeyguardShowing(true) + + // Test + val entry = createFsiEntry(fsi) + fsiChromeRepo.launchFullscreenIntent(entry) + + // Verify + Mockito.verify(dreamManager, times(1)).awaken() + } + + @Test + fun testLaunchFullscreenIntent_updatesFsiInfoFlow() { + // Setup + featureFlags.set(Flags.FSI_CHROME, true) + keyguardRepo.setKeyguardShowing(true) + + // Test + val entry = createFsiEntry(fsi) + fsiChromeRepo.launchFullscreenIntent(entry) + + // Verify + val expectedFsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi) + assertEquals(expectedFsiInfo, fsiChromeRepo.infoFlow.value) + } + + @Test + fun testLaunchFullscreenIntent_notifyFsiLaunched() { + // Setup + featureFlags.set(Flags.FSI_CHROME, true) + keyguardRepo.setKeyguardShowing(true) + + // Test + val entry = createFsiEntry(fsi) + fsiChromeRepo.launchFullscreenIntent(entry) + + // Verify + Mockito.verify(entry, times(1)).notifyFullScreenIntentLaunched() + } + + @Test + fun testLaunchFullscreenIntent_wakesUpDevice() { + // Setup + featureFlags.set(Flags.FSI_CHROME, true) + keyguardRepo.setKeyguardShowing(true) + + // Test + val entry = createFsiEntry(fsi) + fsiChromeRepo.launchFullscreenIntent(entry) + + // Verify + Mockito.verify(centralSurfaces, times(1)).wakeUpForFullScreenIntent() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt new file mode 100644 index 000000000000..33b94e39c019 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt @@ -0,0 +1,127 @@ +/* + * 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.statusbar.notification.logging + +import android.app.Notification +import android.app.StatsManager +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.testing.AndroidTestingRunner +import android.util.StatsEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.system.SysUiStatsLog +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class NotificationMemoryLoggerTest : SysuiTestCase() { + + private val bgExecutor = FakeExecutor(FakeSystemClock()) + private val immediate = Dispatchers.Main.immediate + + @Mock private lateinit var statsManager: StatsManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun onInit_registersCallback() { + val logger = createLoggerWithNotifications(listOf()) + logger.init() + verify(statsManager) + .setPullAtomCallback(SysUiStatsLog.NOTIFICATION_MEMORY_USE, null, bgExecutor, logger) + } + + @Test + fun onPullAtom_wrongAtomId_returnsSkip() { + val logger = createLoggerWithNotifications(listOf()) + val data: MutableList<StatsEvent> = mutableListOf() + assertThat(logger.onPullAtom(111, data)).isEqualTo(StatsManager.PULL_SKIP) + assertThat(data).isEmpty() + } + + @Test + fun onPullAtom_emptyNotifications_returnsZeros() { + val logger = createLoggerWithNotifications(listOf()) + val data: MutableList<StatsEvent> = mutableListOf() + assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data)) + .isEqualTo(StatsManager.PULL_SUCCESS) + assertThat(data).isEmpty() + } + + @Test + fun onPullAtom_notificationPassed_populatesData() { + val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)) + val notification = + Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build() + val logger = createLoggerWithNotifications(listOf(notification)) + val data: MutableList<StatsEvent> = mutableListOf() + + assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data)) + .isEqualTo(StatsManager.PULL_SUCCESS) + assertThat(data).hasSize(1) + } + + @Test + fun onPullAtom_multipleNotificationsPassed_populatesData() { + val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)) + val notification = + Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build() + val iconTwo = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)) + + val notificationTwo = + Notification.Builder(context) + .setStyle(Notification.BigTextStyle().bigText("text")) + .setSmallIcon(iconTwo) + .setContentTitle("titleTwo") + .build() + val logger = createLoggerWithNotifications(listOf(notification, notificationTwo)) + val data: MutableList<StatsEvent> = mutableListOf() + + assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data)) + .isEqualTo(StatsManager.PULL_SUCCESS) + assertThat(data).hasSize(2) + } + + private fun createLoggerWithNotifications( + notifications: List<Notification> + ): NotificationMemoryLogger { + val pipeline: NotifPipeline = mock() + val notifications = + notifications.map { notification -> + NotificationEntryBuilder().setTag("test").setNotification(notification).build() + } + whenever(pipeline.allNotifs).thenReturn(notifications) + return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt index f69839b7087c..072a497f1a65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt @@ -23,6 +23,7 @@ import android.app.Person import android.content.Intent import android.graphics.Bitmap import android.graphics.drawable.Icon +import android.stats.sysui.NotificationEnums import android.testing.AndroidTestingRunner import android.widget.RemoteViews import androidx.test.filters.SmallTest @@ -50,7 +51,27 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 3316, bigPicture = 0, extender = 0, - style = null, + style = NotificationEnums.STYLE_NONE, + styleIcon = 0, + hasCustomView = false, + ) + } + + @Test + fun currentNotificationMemoryUse_rankerGroupNotification() { + val notification = createBasicNotification().build() + val memoryUse = + NotificationMemoryMeter.notificationMemoryUse( + createNotificationEntry(createBasicNotification().setGroup("ranker_group").build()) + ) + assertNotificationObjectSizes( + memoryUse, + smallIcon = notification.smallIcon.bitmap.allocationByteCount, + largeIcon = notification.getLargeIcon().bitmap.allocationByteCount, + extras = 3316, + bigPicture = 0, + extender = 0, + style = NotificationEnums.STYLE_RANKER_GROUP, styleIcon = 0, hasCustomView = false, ) @@ -69,7 +90,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 3316, bigPicture = 0, extender = 0, - style = null, + style = NotificationEnums.STYLE_NONE, styleIcon = 0, hasCustomView = false, ) @@ -92,7 +113,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 3384, bigPicture = 0, extender = 0, - style = null, + style = NotificationEnums.STYLE_NONE, styleIcon = 0, hasCustomView = true, ) @@ -112,7 +133,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 3212, bigPicture = 0, extender = 0, - style = null, + style = NotificationEnums.STYLE_NONE, styleIcon = 0, hasCustomView = false, ) @@ -141,7 +162,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 4092, bigPicture = bigPicture.bitmap.allocationByteCount, extender = 0, - style = "BigPictureStyle", + style = NotificationEnums.STYLE_BIG_PICTURE, styleIcon = bigPictureIcon.bitmap.allocationByteCount, hasCustomView = false, ) @@ -167,7 +188,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 4084, bigPicture = 0, extender = 0, - style = "CallStyle", + style = NotificationEnums.STYLE_CALL, styleIcon = personIcon.bitmap.allocationByteCount, hasCustomView = false, ) @@ -203,7 +224,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 5024, bigPicture = 0, extender = 0, - style = "MessagingStyle", + style = NotificationEnums.STYLE_MESSAGING, styleIcon = personIcon.bitmap.allocationByteCount + historicPersonIcon.bitmap.allocationByteCount, @@ -225,7 +246,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 3612, bigPicture = 0, extender = 556656, - style = null, + style = NotificationEnums.STYLE_NONE, styleIcon = 0, hasCustomView = false, ) @@ -246,7 +267,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras = 3820, bigPicture = 0, extender = 388 + wearBackground.allocationByteCount, - style = null, + style = NotificationEnums.STYLE_NONE, styleIcon = 0, hasCustomView = false, ) @@ -272,7 +293,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { extras: Int, bigPicture: Int, extender: Int, - style: String?, + style: Int, styleIcon: Int, hasCustomView: Boolean, ) { @@ -282,11 +303,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() { assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon) assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon) assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture) - if (style == null) { - assertThat(memoryUse.objectUsage.style).isNull() - } else { - assertThat(memoryUse.objectUsage.style).isEqualTo(style) - } + assertThat(memoryUse.objectUsage.style).isEqualTo(style) assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon) assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt index 3a16fb33388b..a0f50486ffff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt @@ -8,6 +8,7 @@ import android.testing.TestableLooper import android.widget.RemoteViews import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.tests.R import com.google.common.truth.Truth.assertThat @@ -39,16 +40,84 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() { fun testViewWalker_plainNotification() { val row = testHelper.createRow() val result = NotificationMemoryViewWalker.getViewUsage(row) - assertThat(result).hasSize(5) - assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0)) - assertThat(result) - .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0)) + assertThat(result).hasSize(3) assertThat(result) .contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0)) assertThat(result) .contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0)) + assertThat(result).contains(NotificationViewUsage(ViewType.TOTAL, 0, 0, 0, 0, 0, 0)) + } + + @Test + fun testViewWalker_plainNotification_withPublicView() { + val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888)) + val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888)) + testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL) + val row = + testHelper.createRow( + Notification.Builder(mContext) + .setContentText("Test") + .setContentTitle("title") + .setSmallIcon(icon) + .setPublicVersion( + Notification.Builder(mContext) + .setContentText("Public Test") + .setContentTitle("title") + .setSmallIcon(publicIcon) + .build() + ) + .build() + ) + val result = NotificationMemoryViewWalker.getViewUsage(row) + assertThat(result).hasSize(4) assertThat(result) - .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0)) + .contains( + NotificationViewUsage( + ViewType.PRIVATE_EXPANDED_VIEW, + icon.bitmap.allocationByteCount, + 0, + 0, + 0, + 0, + icon.bitmap.allocationByteCount + ) + ) + assertThat(result) + .contains( + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + icon.bitmap.allocationByteCount, + 0, + 0, + 0, + 0, + icon.bitmap.allocationByteCount + ) + ) + assertThat(result) + .contains( + NotificationViewUsage( + ViewType.PUBLIC_VIEW, + publicIcon.bitmap.allocationByteCount, + 0, + 0, + 0, + 0, + publicIcon.bitmap.allocationByteCount + ) + ) + assertThat(result) + .contains( + NotificationViewUsage( + ViewType.TOTAL, + icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount, + 0, + 0, + 0, + 0, + icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount + ) + ) } @Test @@ -67,7 +136,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() { .build() ) val result = NotificationMemoryViewWalker.getViewUsage(row) - assertThat(result).hasSize(5) + assertThat(result).hasSize(3) assertThat(result) .contains( NotificationViewUsage( @@ -95,8 +164,20 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() { icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount ) ) - // Due to deduplication, this should all be 0. - assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0)) + assertThat(result) + .contains( + NotificationViewUsage( + ViewType.TOTAL, + icon.bitmap.allocationByteCount, + largeIcon.bitmap.allocationByteCount, + 0, + bigPicture.allocationByteCount, + 0, + bigPicture.allocationByteCount + + icon.bitmap.allocationByteCount + + largeIcon.bitmap.allocationByteCount + ) + ) } @Test @@ -117,7 +198,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() { .build() ) val result = NotificationMemoryViewWalker.getViewUsage(row) - assertThat(result).hasSize(5) + assertThat(result).hasSize(3) assertThat(result) .contains( NotificationViewUsage( @@ -142,7 +223,17 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() { bitmap.allocationByteCount + icon.bitmap.allocationByteCount ) ) - // Due to deduplication, this should all be 0. - assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0)) + assertThat(result) + .contains( + NotificationViewUsage( + ViewType.TOTAL, + icon.bitmap.allocationByteCount, + 0, + 0, + 0, + bitmap.allocationByteCount, + bitmap.allocationByteCount + icon.bitmap.allocationByteCount + ) + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt index 5f5769572008..3f61af0425de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.FakeShadowView import com.android.systemui.statusbar.notification.NotificationUtils +import com.android.systemui.statusbar.notification.SourceType import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -83,4 +84,17 @@ class ActivatableNotificationViewTest : SysuiTestCase() { mView.updateBackgroundColors() assertThat(mView.currentBackgroundTint).isEqualTo(mNormalColor) } + + @Test + fun roundnessShouldBeTheSame_after_onDensityOrFontScaleChanged() { + val roundableState = mView.roundableState + assertThat(mView.topRoundness).isEqualTo(0f) + mView.requestTopRoundness(1f, SourceType.from("")) + assertThat(mView.topRoundness).isEqualTo(1f) + + mView.onDensityOrFontScaleChanged() + + assertThat(mView.topRoundness).isEqualTo(1f) + assertThat(mView.roundableState.hashCode()).isEqualTo(roundableState.hashCode()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 521e51846834..ae60c73b89b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.StatusBarState.SHADE; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -831,7 +834,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); try { mCentralSurfaces.handleVisibleToUserChanged(true); @@ -850,7 +853,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); try { mCentralSurfaces.handleVisibleToUserChanged(true); @@ -991,7 +994,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void testShowKeyguardImplementation_setsState() { when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>()); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); // By default, showKeyguardImpl sets state to KEYGUARD. mCentralSurfaces.showKeyguardImpl(); @@ -1048,7 +1051,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void collapseShade_callsanimateCollapseShade_whenExpanded() { // GIVEN the shade is expanded mCentralSurfaces.onShadeExpansionFullyChanged(true); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); // WHEN collapseShade is called mCentralSurfaces.collapseShade(); @@ -1061,7 +1064,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() { // GIVEN the shade is collapsed mCentralSurfaces.onShadeExpansionFullyChanged(false); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); // WHEN collapseShade is called mCentralSurfaces.collapseShade(); @@ -1074,7 +1077,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() { // GIVEN the shade is expanded & flag enabled mCentralSurfaces.onShadeExpansionFullyChanged(true); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, false); // WHEN collapseShadeForBugreport is called @@ -1088,7 +1091,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() { // GIVEN the shade is expanded & flag enabled mCentralSurfaces.onShadeExpansionFullyChanged(true); - mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); + mCentralSurfaces.setBarStateForTest(SHADE); mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, true); // WHEN collapseShadeForBugreport is called @@ -1100,10 +1103,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() { - when(mKeyguardStateController.isShowing()).thenReturn(false); setFoldedStates(FOLD_STATE_FOLDED); setGoToSleepStates(FOLD_STATE_FOLDED); - when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(true); + mCentralSurfaces.setBarStateForTest(SHADE); + when(mNotificationPanelViewController.isShadeFullyOpen()).thenReturn(true); setDeviceState(FOLD_STATE_UNFOLDED); @@ -1112,10 +1115,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test public void deviceStateChange_unfolded_shadeOpen_onKeyguard_doesNotSetLeaveOpenOnKeyguardHide() { - when(mKeyguardStateController.isShowing()).thenReturn(true); setFoldedStates(FOLD_STATE_FOLDED); setGoToSleepStates(FOLD_STATE_FOLDED); - when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(true); + mCentralSurfaces.setBarStateForTest(KEYGUARD); + when(mNotificationPanelViewController.isShadeFullyOpen()).thenReturn(true); setDeviceState(FOLD_STATE_UNFOLDED); @@ -1127,7 +1130,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { public void deviceStateChange_unfolded_shadeClose_doesNotSetLeaveOpenOnKeyguardHide() { setFoldedStates(FOLD_STATE_FOLDED); setGoToSleepStates(FOLD_STATE_FOLDED); - when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(false); + mCentralSurfaces.setBarStateForTest(SHADE); + when(mNotificationPanelViewController.isShadeFullyOpen()).thenReturn(false); setDeviceState(FOLD_STATE_UNFOLDED); @@ -1161,12 +1165,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // it to remain visible. when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true); setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */); - verify(mStatusBarStateController, never()).setState(StatusBarState.SHADE); + verify(mStatusBarStateController, never()).setState(SHADE); // Once the animation ends, verify that the keyguard is actually hidden. when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false); setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */); - verify(mStatusBarStateController).setState(StatusBarState.SHADE); + verify(mStatusBarStateController).setState(SHADE); } @Test @@ -1179,7 +1183,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // immediately hide the keyguard. when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false); setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */); - verify(mStatusBarStateController).setState(StatusBarState.SHADE); + verify(mStatusBarStateController).setState(SHADE); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index cae414a3dc67..19658e6398c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -55,6 +55,7 @@ import com.android.systemui.ActivityIntentHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -220,7 +221,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationPanelViewController.class), mActivityLaunchAnimator, notificationAnimationProvider, - mock(LaunchFullScreenIntentProvider.class) + mock(LaunchFullScreenIntentProvider.class), + mock(FeatureFlags.class) ); // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS new file mode 100644 index 000000000000..7ccb316dbca5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS @@ -0,0 +1,8 @@ +# Bug component: 1254381 +azappone@google.com +achalke@google.com +juliacr@google.com +madym@google.com +mgalhardo@google.com +petrcermak@google.com +vanjan@google.com
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 5c2a915e81b6..55019490bdcd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -120,6 +120,14 @@ class FakeKeyguardRepository : KeyguardRepository { _dozeAmount.value = dozeAmount } + fun setWakefulnessModel(model: WakefulnessModel) { + _wakefulnessModel.value = model + } + + fun setBouncerShowing(isShowing: Boolean) { + _isBouncerShowing.value = isShowing + } + fun setBiometricUnlockState(state: BiometricUnlockModel) { _biometricUnlockState.tryEmit(state) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt new file mode 100644 index 000000000000..2c0a8fde0d77 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -0,0 +1,33 @@ +/* + * 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.shade.data.repository + +import com.android.systemui.shade.domain.model.ShadeModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +/** Fake implementation of [KeyguardRepository] */ +class FakeShadeRepository : ShadeRepository { + + private val _shadeModel = MutableStateFlow(ShadeModel()) + override val shadeModel: Flow<ShadeModel> = _shadeModel + + fun setShadeModel(model: ShadeModel) { + _shadeModel.value = model + } +} diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index e529010e6b7d..daba4c0e83a0 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -79,7 +79,7 @@ public class GestureLauncherService extends SystemService { * completed faster than this, we assume it's not performed by human and the * event gets ignored. */ - @VisibleForTesting static final int EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS = 160; + @VisibleForTesting static final int EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS = 200; /** * Interval in milliseconds in which the power button must be depressed in succession to be diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index d328fd7cacbb..bb1e3931c02e 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -56,6 +56,7 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.service.dreams.DreamService; @@ -113,6 +114,7 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; private final ComponentName mAmbientDisplayComponent; @@ -212,6 +214,7 @@ public final class DreamManagerService extends SystemService { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = getLocalService(PowerManagerInternal.class); mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); + mUserManager = context.getSystemService(UserManager.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); mUiEventLogger = new UiEventLoggerImpl(); @@ -383,6 +386,10 @@ public final class DreamManagerService extends SystemService { return false; } + if (!mUserManager.isUserUnlocked()) { + return false; + } + if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) { return mIsCharging; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e639866a6bab..418e1edca05b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7756,10 +7756,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // configuration. This is important to cases where activities with incompatible // orientations launch, or user goes back from an activity of bi-orientation to an // activity with specified orientation. - if (getRequestedOrientation() == SCREEN_ORIENTATION_UNSET) { - return; - } - if (onDescendantOrientationChanged(this)) { // WM Shell can show additional UI elements, e.g. a restart button for size compat mode // so ensure that WM Shell is called when an activity becomes visible. @@ -8329,7 +8325,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If orientation is respected when insets are applied, then stableBounds will be empty. boolean orientationRespectedWithInsets = orientationRespectedWithInsets(parentBounds, stableBounds); - if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) { + if (orientationRespectedWithInsets + && handlesOrientationChangeFromDescendant(mOrientation)) { // No need to letterbox because of fixed orientation. Display will handle // fixed-orientation requests and a display rotation is enough to respect requested // orientation with insets applied. diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index a32e46078d82..af5bd14baf31 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -34,6 +34,8 @@ import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainerChildProto.DISPLAY_AREA; import android.annotation.Nullable; +import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; @@ -141,26 +143,30 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { } @Override + @ScreenOrientation int getOrientation(int candidate) { - mLastOrientationSource = null; - if (getIgnoreOrientationRequest()) { + final int orientation = super.getOrientation(candidate); + if (getIgnoreOrientationRequest(orientation)) { + // In all the other case, mLastOrientationSource will be reassigned to a new value + mLastOrientationSource = null; return SCREEN_ORIENTATION_UNSET; } - - return super.getOrientation(candidate); + return orientation; } @Override - boolean handlesOrientationChangeFromDescendant() { - return !getIgnoreOrientationRequest() - && super.handlesOrientationChangeFromDescendant(); + boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) { + return !getIgnoreOrientationRequest(orientation) + && super.handlesOrientationChangeFromDescendant(orientation); } @Override - boolean onDescendantOrientationChanged(WindowContainer requestingContainer) { + boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) { // If this is set to ignore the orientation request, we don't propagate descendant // orientation request. - return !getIgnoreOrientationRequest() + final int orientation = requestingContainer != null + ? requestingContainer.mOrientation : SCREEN_ORIENTATION_UNSET; + return !getIgnoreOrientationRequest(orientation) && super.onDescendantOrientationChanged(requestingContainer); } @@ -224,6 +230,23 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { } } + /** + * @return {@value true} if we need to ignore the orientation in input. + */ + // TODO(b/262366204): Rename getIgnoreOrientationRequest to shouldIgnoreOrientationRequest + boolean getIgnoreOrientationRequest(@ScreenOrientation int orientation) { + // We always respect orientation request for ActivityInfo.SCREEN_ORIENTATION_LOCKED + // ActivityInfo.SCREEN_ORIENTATION_NOSENSOR. + // Main use case why this is important is Camera apps that rely on those + // properties to ensure that they will be able to determine Camera preview + // orientation correctly + if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED + || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { + return false; + } + return getIgnoreOrientationRequest(); + } + boolean getIgnoreOrientationRequest() { // Adding an exception for when ignoreOrientationRequest is overridden at runtime for all // DisplayArea-s. For example, this is needed for the Kids Mode since many Kids apps aren't @@ -640,11 +663,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { } @Override + @ScreenOrientation int getOrientation(int candidate) { mLastOrientationSource = null; - if (getIgnoreOrientationRequest()) { - return SCREEN_ORIENTATION_UNSET; - } // Find a window requesting orientation. final WindowState win = getWindow(mGetOrientingWindow); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5d502f5420ca..4c499861cb9a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1558,13 +1558,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override - boolean onDescendantOrientationChanged(WindowContainer requestingContainer) { + boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) { final Configuration config = updateOrientation( requestingContainer, false /* forceUpdate */); // If display rotation class tells us that it doesn't consider app requested orientation, // this display won't rotate just because of an app changes its requested orientation. Thus // it indicates that this display chooses not to handle this request. - final boolean handled = handlesOrientationChangeFromDescendant(); + final int orientation = requestingContainer != null ? requestingContainer.mOrientation + : SCREEN_ORIENTATION_UNSET; + final boolean handled = handlesOrientationChangeFromDescendant(orientation); if (config == null) { return handled; } @@ -1587,8 +1589,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override - boolean handlesOrientationChangeFromDescendant() { - return !getIgnoreOrientationRequest() + boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) { + return !getIgnoreOrientationRequest(orientation) && !getDisplayRotation().isFixedToUserRotation(); } @@ -1689,7 +1691,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return ROTATION_UNDEFINED; } if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM - || getIgnoreOrientationRequest()) { + || getIgnoreOrientationRequest(r.mOrientation)) { return ROTATION_UNDEFINED; } if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { @@ -2688,15 +2690,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @ScreenOrientation @Override int getOrientation() { - mLastOrientationSource = null; - if (!handlesOrientationChangeFromDescendant()) { - // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Display id=%d is ignoring all orientation requests, return %d", - mDisplayId, SCREEN_ORIENTATION_UNSPECIFIED); - return SCREEN_ORIENTATION_UNSPECIFIED; - } - if (mWmService.mDisplayFrozen) { if (mWmService.mPolicy.isKeyguardLocked()) { // Use the last orientation the while the display is frozen with the keyguard @@ -2712,6 +2705,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } final int orientation = super.getOrientation(); + + if (!handlesOrientationChangeFromDescendant(orientation)) { + mLastOrientationSource = null; + // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Display id=%d is ignoring orientation request for %d, return %d", + mDisplayId, orientation, SCREEN_ORIENTATION_UNSPECIFIED); + return SCREEN_ORIENTATION_UNSPECIFIED; + } + if (orientation == SCREEN_ORIENTATION_UNSET) { // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation ProtoLog.v(WM_DEBUG_ORIENTATION, @@ -3832,18 +3835,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Called when the focused {@link TaskDisplayArea} on this display may have changed. */ void onLastFocusedTaskDisplayAreaChanged(@Nullable TaskDisplayArea taskDisplayArea) { - // Only record the TaskDisplayArea that handles orientation request. - if (taskDisplayArea != null && taskDisplayArea.handlesOrientationChangeFromDescendant()) { - mOrientationRequestingTaskDisplayArea = taskDisplayArea; - return; - } - - // If the previous TDA no longer handles orientation request, clear it. - if (mOrientationRequestingTaskDisplayArea != null - && !mOrientationRequestingTaskDisplayArea - .handlesOrientationChangeFromDescendant()) { - mOrientationRequestingTaskDisplayArea = null; - } + mOrientationRequestingTaskDisplayArea = taskDisplayArea; } /** @@ -5053,13 +5045,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @Override - int getOrientation(int candidate) { - if (getIgnoreOrientationRequest()) { - return SCREEN_ORIENTATION_UNSET; - } - + @ScreenOrientation + int getOrientation(@ScreenOrientation int candidate) { // IME does not participate in orientation. - return candidate; + return getIgnoreOrientationRequest(candidate) ? SCREEN_ORIENTATION_UNSET : candidate; } @Override diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 33d8acd31fe8..ea6f2442a919 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -143,6 +143,7 @@ import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -2686,8 +2687,8 @@ class Task extends TaskFragment { } @Override - boolean handlesOrientationChangeFromDescendant() { - if (!super.handlesOrientationChangeFromDescendant()) { + boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) { + if (!super.handlesOrientationChangeFromDescendant(orientation)) { return false; } @@ -2702,7 +2703,7 @@ class Task extends TaskFragment { // Check for leaf Task. // Display won't rotate for the orientation request if the Task/TaskDisplayArea // can't specify orientation. - return canSpecifyOrientation() && getDisplayArea().canSpecifyOrientation(); + return canSpecifyOrientation() && getDisplayArea().canSpecifyOrientation(orientation); } void resize(boolean relayout, boolean forced) { @@ -2772,6 +2773,13 @@ class Task extends TaskFragment { @Override void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, Rect outSurfaceInsets) { + // If this task has its adjacent task, it means they should animate together. Use display + // bounds for them could move same as full screen task. + if (getAdjacentTaskFragment() != null && getAdjacentTaskFragment().asTask() != null) { + super.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); + return; + } + final WindowState windowState = getTopVisibleAppMainWindow(); if (windowState != null) { windowState.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index e0ed356fbe59..8ad76a3eb327 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -41,6 +41,7 @@ import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.graphics.Color; import android.os.UserHandle; @@ -633,22 +634,20 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } @Override - int getOrientation(int candidate) { - mLastOrientationSource = null; - if (getIgnoreOrientationRequest()) { - return SCREEN_ORIENTATION_UNSET; - } - if (!canSpecifyOrientation()) { + @ScreenOrientation + int getOrientation(@ScreenOrientation int candidate) { + final int orientation = super.getOrientation(candidate); + if (!canSpecifyOrientation(orientation)) { + mLastOrientationSource = null; // We only respect orientation of the focused TDA, which can be a child of this TDA. - return reduceOnAllTaskDisplayAreas((taskDisplayArea, orientation) -> { - if (taskDisplayArea == this || orientation != SCREEN_ORIENTATION_UNSET) { - return orientation; + return reduceOnAllTaskDisplayAreas((taskDisplayArea, taskOrientation) -> { + if (taskDisplayArea == this || taskOrientation != SCREEN_ORIENTATION_UNSET) { + return taskOrientation; } return taskDisplayArea.getOrientation(candidate); }, SCREEN_ORIENTATION_UNSET); } - final int orientation = super.getOrientation(candidate); if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) { ProtoLog.v(WM_DEBUG_ORIENTATION, @@ -1870,12 +1869,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } /** Whether this task display area can request orientation. */ - boolean canSpecifyOrientation() { - // Only allow to specify orientation if this TDA is not set to ignore orientation request, - // and it is the last focused one on this logical display that can request orientation - // request. - return !getIgnoreOrientationRequest() - && mDisplayContent.getOrientationRequestingTaskDisplayArea() == this; + boolean canSpecifyOrientation(@ScreenOrientation int orientation) { + // Only allow to specify orientation if this TDA is the last focused one on this logical + // display that can request orientation request. + return mDisplayContent.getOrientationRequestingTaskDisplayArea() == this + && !getIgnoreOrientationRequest(orientation); } void clearPreferredTopFocusableRootTask() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 5c893de6b920..cb5a4338c567 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -1419,9 +1420,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @return {@code true} if it handles or will handle orientation change in the future; {@code * false} if it won't handle the change at anytime. */ - boolean handlesOrientationChangeFromDescendant() { + boolean handlesOrientationChangeFromDescendant(int orientation) { final WindowContainer parent = getParent(); - return parent != null && parent.handlesOrientationChangeFromDescendant(); + return parent != null && parent.handlesOrientationChangeFromDescendant(orientation); } /** @@ -1513,7 +1514,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // portrait but the task is still in landscape. While updating from display, // the task can be updated to portrait first so the configuration can be // computed in a consistent environment. - && (inMultiWindowMode() || !handlesOrientationChangeFromDescendant())) { + && (inMultiWindowMode() + || !handlesOrientationChangeFromDescendant(orientation))) { // Resolve the requested orientation. onConfigurationChanged(parent.getConfiguration()); } @@ -3186,7 +3188,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (isOrganized() // TODO(b/161711458): Clean-up when moved to shell. && getWindowingMode() != WINDOWING_MODE_FULLSCREEN - && getWindowingMode() != WINDOWING_MODE_FREEFORM) { + && getWindowingMode() != WINDOWING_MODE_FREEFORM + && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW) { return null; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 17ec19d3e617..a017bd6a9436 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -517,7 +517,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Mimic the behavior that display doesn't handle app's requested orientation. final DisplayContent dc = activity.getTask().getDisplayContent(); doReturn(false).when(dc).onDescendantOrientationChanged(any()); - doReturn(false).when(dc).handlesOrientationChangeFromDescendant(); + doReturn(false).when(dc).handlesOrientationChangeFromDescendant(anyInt()); final int requestedOrientation; switch (newConfig.orientation) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index b87c5a364c82..10540dc5a9ee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -471,29 +473,28 @@ public class DisplayAreaTest extends WindowTestsBase { } @Test - public void testSetIgnoreOrientationRequest() { - final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test"); - final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY); - spyOn(token); - doReturn(mock(DisplayContent.class)).when(token).getDisplayContent(); - doNothing().when(token).setParent(any()); - final WindowState win = createWindowState(token); - spyOn(win); - doNothing().when(win).setParent(any()); - win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - token.addChild(win, 0); - area.addChild(token); - doReturn(true).when(win).isVisible(); + public void testSetIgnoreOrientationRequest_notCallSuperOnDescendantOrientationChanged() { + final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); + final Task stack = + new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); + final ActivityRecord activity = stack.getTopNonFinishingActivity(); - assertEquals(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, area.getOrientation()); + tda.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - area.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, area.getOrientation()); + verify(tda).onDescendantOrientationChanged(any()); + verify(mDisplayContent, never()).onDescendantOrientationChanged(any()); + + tda.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); + activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + + verify(tda, times(2)).onDescendantOrientationChanged(any()); + verify(mDisplayContent).onDescendantOrientationChanged(any()); } @Test - public void testSetIgnoreOrientationRequest_notCallSuperOnDescendantOrientationChanged() { + public void testSetIgnoreOrientationRequest_callSuperOnDescendantOrientationChangedNoSensor() { final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); final Task stack = new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); @@ -501,20 +502,41 @@ public class DisplayAreaTest extends WindowTestsBase { tda.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + activity.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); verify(tda).onDescendantOrientationChanged(any()); - verify(mDisplayContent, never()).onDescendantOrientationChanged(any()); + verify(mDisplayContent).onDescendantOrientationChanged(any()); tda.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); - activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + activity.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); - verify(tda, times(2)).onDescendantOrientationChanged(any()); + verify(tda).onDescendantOrientationChanged(any()); + verify(mDisplayContent).onDescendantOrientationChanged(any()); + } + + @Test + public void testSetIgnoreOrientationRequest_callSuperOnDescendantOrientationChangedLocked() { + final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); + final Task stack = + new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); + final ActivityRecord activity = stack.getTopNonFinishingActivity(); + + tda.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + activity.setRequestedOrientation(SCREEN_ORIENTATION_LOCKED); + + verify(tda).onDescendantOrientationChanged(any()); + verify(mDisplayContent).onDescendantOrientationChanged(any()); + + tda.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); + activity.setRequestedOrientation(SCREEN_ORIENTATION_LOCKED); + + verify(tda).onDescendantOrientationChanged(any()); verify(mDisplayContent).onDescendantOrientationChanged(any()); } @Test - public void testSetIgnoreOrientationRequest_updateOrientationRequestingTaskDisplayArea() { + public void testGetOrientationRequestingTaskDisplayArea_updateOrientationTaskDisplayArea() { final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); final Task stack = new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); @@ -526,7 +548,7 @@ public class DisplayAreaTest extends WindowTestsBase { // TDA is no longer handling orientation request, clear the last focused TDA. tda.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - assertThat(mDisplayContent.getOrientationRequestingTaskDisplayArea()).isNull(); + assertThat(mDisplayContent.getOrientationRequestingTaskDisplayArea()).isEqualTo(tda); // TDA now handles orientation request, update last focused TDA based on the focused app. tda.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index 3ab4495bd7ca..232b9b2897cc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -163,6 +165,30 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test + public void testIgnoreOrientationRequest_displayReceiveOrientationChangeForNoSensor() { + mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); + + prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); + + verify(mFirstRoot).onDescendantOrientationChanged(any()); + verify(mDisplay).onDescendantOrientationChanged(any()); + } + + @Test + public void testIgnoreOrientationRequest_displayReceiveOrientationChangeForLocked() { + mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); + + prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED); + + verify(mFirstRoot).onDescendantOrientationChanged(any()); + verify(mDisplay).onDescendantOrientationChanged(any()); + } + + @Test public void testLaunchPortraitApp_fillsDisplayAreaGroup() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -215,6 +241,21 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test + public void testLaunchNoSensorApp_noSizeCompatAfterRotation() { + mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); + + prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); + assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); + + rotateDisplay(mDisplay, ROTATION_90); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); + assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); + } + + @Test public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -236,6 +277,26 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test + public void testLaunchNoSensorApp_activityIsNotLetterboxForFixedOrientationDisplayAreaGroup() { + mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); + + prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); + } + + @Test + public void testLaunchLockedApp_activityIsNotLetterboxForFixedOrientationInDisplayAreaGroup() { + mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); + + prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); + } + + @Test public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -265,6 +326,20 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test + public void testLaunchNoSensorApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { + mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); + + prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); + + rotateDisplay(mDisplay, ROTATION_90); + + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); + assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); + } + + @Test public void testPlaceImeContainer_reparentToTargetDisplayAreaGroup() { setupImeWindow(); final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index e660db57fb69..582ddf21d7f9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -29,8 +29,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -422,24 +423,63 @@ public class TaskDisplayAreaTests extends WindowTestsBase { // Activity on TDA1 is focused mDisplayContent.setFocusedApp(firstActivity); - assertThat(firstTaskDisplayArea.canSpecifyOrientation()).isTrue(); - assertThat(secondTaskDisplayArea.canSpecifyOrientation()).isFalse(); + final int testOrientation = SCREEN_ORIENTATION_PORTRAIT; + + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); // No focused app, TDA1 is still recorded as last focused. mDisplayContent.setFocusedApp(null); - assertThat(firstTaskDisplayArea.canSpecifyOrientation()).isTrue(); - assertThat(secondTaskDisplayArea.canSpecifyOrientation()).isFalse(); + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); // Activity on TDA2 is focused mDisplayContent.setFocusedApp(secondActivity); - assertThat(firstTaskDisplayArea.canSpecifyOrientation()).isFalse(); - assertThat(secondTaskDisplayArea.canSpecifyOrientation()).isTrue(); + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); + } + + @Test + public void testCanSpecifyOrientation() { + final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", + FEATURE_VENDOR_FIRST); + final Task firstRootTask = firstTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final Task secondRootTask = secondTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm) + .setTask(firstRootTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mAtm) + .setTask(secondRootTask).build(); + firstTaskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + secondTaskDisplayArea.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); + + final int testOrientation = SCREEN_ORIENTATION_PORTRAIT; + + // Activity on TDA1 is focused, but TDA1 cannot specify orientation because + // ignoreOrientationRequest is true + // Activity on TDA2 has ignoreOrientationRequest false but it doesn't have focus so it + // cannot specify orientation + mDisplayContent.setFocusedApp(firstActivity); + + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + + // Activity on TDA1 is not focused, and so it cannot specify orientation + // Activity on TDA2 is focused, and it can specify orientation because + // ignoreOrientationRequest is false + mDisplayContent.setFocusedApp(secondActivity); + + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); } @Test - public void testIsLastFocused_onlyCountIfTaskDisplayAreaHandlesOrientationRequest() { + public void testCanSpecifyOrientationNoSensor() { final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", @@ -455,34 +495,51 @@ public class TaskDisplayAreaTests extends WindowTestsBase { firstTaskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); secondTaskDisplayArea.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); - // Activity on TDA1 is focused, but TDA1 doesn't respect orientation request + final int testOrientation = SCREEN_ORIENTATION_NOSENSOR; + + // ignoreOrientationRequest is always false for SCREEN_ORIENTATION_NOSENSOR so + // only the TDAs with focus can specify orientations mDisplayContent.setFocusedApp(firstActivity); - assertThat(firstTaskDisplayArea.canSpecifyOrientation()).isFalse(); - assertThat(secondTaskDisplayArea.canSpecifyOrientation()).isFalse(); + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); - // Activity on TDA2 is focused, and TDA2 respects orientation request mDisplayContent.setFocusedApp(secondActivity); - assertThat(firstTaskDisplayArea.canSpecifyOrientation()).isFalse(); - assertThat(secondTaskDisplayArea.canSpecifyOrientation()).isTrue(); + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); } @Test - public void testIgnoreOrientationRequest() { - final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final Task task = taskDisplayArea.createRootTask( + public void testCanSpecifyOrientationLocked() { + final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", + FEATURE_VENDOR_FIRST); + final Task firstRootTask = firstTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); + final Task secondRootTask = secondTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm) + .setTask(firstRootTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mAtm) + .setTask(secondRootTask).build(); + firstTaskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + secondTaskDisplayArea.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); - mDisplayContent.setFocusedApp(activity); - activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + final int testOrientation = SCREEN_ORIENTATION_LOCKED; - assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE); + // ignoreOrientationRequest is always false for SCREEN_ORIENTATION_NOSENSOR so + // only the TDAs with focus can specify orientations + mDisplayContent.setFocusedApp(firstActivity); - taskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + + mDisplayContent.setFocusedApp(secondActivity); - assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET); + assertThat(firstTaskDisplayArea.canSpecifyOrientation(testOrientation)).isFalse(); + assertThat(secondTaskDisplayArea.canSpecifyOrientation(testOrientation)).isTrue(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 0c8e89a8d010..eac67770cf36 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -57,6 +57,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; @@ -392,12 +393,16 @@ public class TaskTests extends WindowTestsBase { leafTask1.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_HOME); leafTask2.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_STANDARD); + // We need to use an orientation that is not an exception for the + // ignoreOrientationRequest flag. + final int orientation = SCREEN_ORIENTATION_PORTRAIT; + assertEquals(leafTask2, rootTask.getTopChild()); - assertTrue(rootTask.handlesOrientationChangeFromDescendant()); + assertTrue(rootTask.handlesOrientationChangeFromDescendant(orientation)); // Treat orientation request from home as handled. - assertTrue(leafTask1.handlesOrientationChangeFromDescendant()); + assertTrue(leafTask1.handlesOrientationChangeFromDescendant(orientation)); // Orientation request from standard activity in multi window will not be handled. - assertFalse(leafTask2.handlesOrientationChangeFromDescendant()); + assertFalse(leafTask2.handlesOrientationChangeFromDescendant(orientation)); } @Test @@ -636,7 +641,8 @@ public class TaskTests extends WindowTestsBase { doReturn(parentWindowContainer).when(task).getParent(); doReturn(display.getDefaultTaskDisplayArea()).when(task).getDisplayArea(); doReturn(rootTask).when(task).getRootTask(); - doReturn(true).when(parentWindowContainer).handlesOrientationChangeFromDescendant(); + doReturn(true).when(parentWindowContainer) + .handlesOrientationChangeFromDescendant(anyInt()); // Setting app to fixed portrait fits within parent, but Task shouldn't adjust the // bounds because its parent says it will handle it at a later time. diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index ef3ddb7b1302..ed7d12384662 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -662,13 +662,13 @@ public class WindowContainerTests extends WindowTestsBase { public void testSetOrientation() { final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).build()); final TestWindowContainer child = spy(root.addChildWindow()); - doReturn(true).when(root).handlesOrientationChangeFromDescendant(); + doReturn(true).when(root).handlesOrientationChangeFromDescendant(anyInt()); child.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN); child.setOrientation(SCREEN_ORIENTATION_PORTRAIT); // The ancestor should decide whether to dispatch the configuration change. verify(child, never()).onConfigurationChanged(any()); - doReturn(false).when(root).handlesOrientationChangeFromDescendant(); + doReturn(false).when(root).handlesOrientationChangeFromDescendant(anyInt()); child.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); // The ancestor doesn't handle the request so the descendant applies the change directly. verify(child).onConfigurationChanged(any()); @@ -843,11 +843,14 @@ public class WindowContainerTests extends WindowTestsBase { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = spy(builder.build()); + // We use an orientation that is not an exception for the ignoreOrientationRequest flag + final int orientation = SCREEN_ORIENTATION_PORTRAIT; + final TestWindowContainer child = root.addChildWindow(); - assertFalse(child.handlesOrientationChangeFromDescendant()); + assertFalse(child.handlesOrientationChangeFromDescendant(orientation)); - Mockito.doReturn(true).when(root).handlesOrientationChangeFromDescendant(); - assertTrue(child.handlesOrientationChangeFromDescendant()); + Mockito.doReturn(true).when(root).handlesOrientationChangeFromDescendant(anyInt()); + assertTrue(child.handlesOrientationChangeFromDescendant(orientation)); } @Test diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java index b9d2ae6b0d39..244b3a013056 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java @@ -19,13 +19,13 @@ package com.android.server.voiceinteraction; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__EMPTY_AUDIO_STREAM_LIST; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ENDED; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ILLEGAL_COPY_BUFFER_SIZE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__INTERRUPTED_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__NO_PERMISSION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__STARTED; import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG; import android.annotation.NonNull; @@ -60,10 +60,10 @@ final class HotwordAudioStreamCopier { private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService"; private static final String TASK_ID_PREFIX = "HotwordDetectedResult@"; private static final String THREAD_NAME_PREFIX = "Copy-"; - private static final int DEFAULT_COPY_BUFFER_LENGTH_BYTES = 2_560; // Corresponds to the OS pipe capacity in bytes private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536; + private static final int DEFAULT_COPY_BUFFER_LENGTH_BYTES = 32_768; private final AppOpsManager mAppOpsManager; private final int mDetectorType; @@ -98,14 +98,17 @@ final class HotwordAudioStreamCopier { throws IOException { List<HotwordAudioStream> audioStreams = result.getAudioStreams(); if (audioStreams.isEmpty()) { - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST, - mVoiceInteractorUid); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__EMPTY_AUDIO_STREAM_LIST, + mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0, + /* streamCount= */ 0); return result; } + final int audioStreamCount = audioStreams.size(); List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size()); List<CopyTaskInfo> copyTaskInfos = new ArrayList<>(audioStreams.size()); + int totalMetadataBundleSizeBytes = 0; for (HotwordAudioStream audioStream : audioStreams) { ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe(); ParcelFileDescriptor clientAudioSource = clientPipe[0]; @@ -117,12 +120,14 @@ final class HotwordAudioStreamCopier { int copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; PersistableBundle metadata = audioStream.getMetadata(); + totalMetadataBundleSizeBytes += HotwordDetectedResult.getParcelableSize(metadata); if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) { copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1); if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) { - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE, - mVoiceInteractorUid); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ILLEGAL_COPY_BUFFER_SIZE, + mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0, + audioStreamCount); Slog.w(TAG, "Attempted to set an invalid copy buffer length (" + copyBufferLength + ") for: " + audioStream); copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; @@ -139,7 +144,9 @@ final class HotwordAudioStreamCopier { } String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result); - mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos)); + mExecutorService.execute( + new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos, + totalMetadataBundleSizeBytes)); return result.buildUpon().setAudioStreams(newAudioStreams).build(); } @@ -159,11 +166,14 @@ final class HotwordAudioStreamCopier { private class HotwordDetectedResultCopyTask implements Runnable { private final String mResultTaskId; private final List<CopyTaskInfo> mCopyTaskInfos; + private final int mTotalMetadataSizeBytes; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); - HotwordDetectedResultCopyTask(String resultTaskId, List<CopyTaskInfo> copyTaskInfos) { + HotwordDetectedResultCopyTask(String resultTaskId, List<CopyTaskInfo> copyTaskInfos, + int totalMetadataSizeBytes) { mResultTaskId = resultTaskId; mCopyTaskInfos = copyTaskInfos; + mTotalMetadataSizeBytes = totalMetadataSizeBytes; } @Override @@ -183,19 +193,38 @@ final class HotwordAudioStreamCopier { mVoiceInteractorUid, mVoiceInteractorPackageName, mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) { try { - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START, - mVoiceInteractorUid); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__STARTED, + mVoiceInteractorUid, /* streamSizeBytes= */ 0, mTotalMetadataSizeBytes, + size); // TODO(b/244599891): Set timeout, close after inactivity mExecutorService.invokeAll(tasks); - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END, - mVoiceInteractorUid); + + int totalStreamSizeBytes = 0; + for (SingleAudioStreamCopyTask task : tasks) { + totalStreamSizeBytes += task.mTotalCopiedBytes; + } + + Slog.i(TAG, mResultTaskId + ": Task was completed. Total bytes streamed: " + + totalStreamSizeBytes + ", total metadata bundle size bytes: " + + mTotalMetadataSizeBytes); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ENDED, + mVoiceInteractorUid, totalStreamSizeBytes, mTotalMetadataSizeBytes, + size); } catch (InterruptedException e) { - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION, - mVoiceInteractorUid); - Slog.e(TAG, mResultTaskId + ": Task was interrupted", e); + int totalStreamSizeBytes = 0; + for (SingleAudioStreamCopyTask task : tasks) { + totalStreamSizeBytes += task.mTotalCopiedBytes; + } + + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__INTERRUPTED_EXCEPTION, + mVoiceInteractorUid, totalStreamSizeBytes, mTotalMetadataSizeBytes, + size); + Slog.e(TAG, mResultTaskId + ": Task was interrupted. Total bytes streamed: " + + totalStreamSizeBytes + ", total metadata bundle size bytes: " + + mTotalMetadataSizeBytes); bestEffortPropagateError(e.getMessage()); } finally { mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, @@ -203,9 +232,10 @@ final class HotwordAudioStreamCopier { mVoiceInteractorAttributionTag); } } else { - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION, - mVoiceInteractorUid); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__NO_PERMISSION, + mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0, + size); bestEffortPropagateError( "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with" + " uid=" + mVoiceInteractorUid @@ -220,9 +250,10 @@ final class HotwordAudioStreamCopier { copyTaskInfo.mSource.closeWithError(errorMessage); copyTaskInfo.mSink.closeWithError(errorMessage); } - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM, - mVoiceInteractorUid); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM, + mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0, + mCopyTaskInfos.size()); } catch (IOException e) { Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e); } @@ -237,6 +268,8 @@ final class HotwordAudioStreamCopier { private final int mDetectorType; private final int mUid; + private volatile int mTotalCopiedBytes = 0; + SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource, ParcelFileDescriptor audioSink, int copyBufferLength, int detectorType, int uid) { mStreamTaskId = streamTaskId; @@ -281,6 +314,7 @@ final class HotwordAudioStreamCopier { Arrays.copyOfRange(buffer, 0, 20))); } fos.write(buffer, 0, bytesRead); + mTotalCopiedBytes += bytesRead; } // TODO(b/244599891): Close PFDs after inactivity } @@ -288,8 +322,10 @@ final class HotwordAudioStreamCopier { mAudioSource.closeWithError(e.getMessage()); mAudioSink.closeWithError(e.getMessage()); Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e); - HotwordMetricsLogger.writeDetectorEvent(mDetectorType, - HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM, mUid); + HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType, + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM, + mUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0, + /* streamCount= */ 0); } finally { if (fis != null) { fis.close(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java index 61c18be6f133..c35d90f4a495 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java @@ -16,6 +16,9 @@ package com.android.server.voiceinteraction; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; @@ -47,6 +50,12 @@ public final class HotwordMetricsLogger { HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; private static final int METRICS_INIT_NORMAL_DETECTOR = HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR; + private static final int AUDIO_EGRESS_DSP_DETECTOR = + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + private static final int AUDIO_EGRESS_SOFTWARE_DETECTOR = + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + private static final int AUDIO_EGRESS_NORMAL_DETECTOR = + HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR; private HotwordMetricsLogger() { // Class only contains static utility functions, and should not be instantiated @@ -97,6 +106,16 @@ public final class HotwordMetricsLogger { metricsDetectorType, event, uid); } + /** + * Logs information related to hotword audio egress events. + */ + public static void writeAudioEgressEvent(int detectorType, int event, int uid, + int streamSizeBytes, int bundleSizeBytes, int streamCount) { + int metricsDetectorType = getAudioEgressDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED, + metricsDetectorType, event, uid, streamSizeBytes, bundleSizeBytes, streamCount); + } + private static int getCreateMetricsDetectorType(int detectorType) { switch (detectorType) { case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: @@ -151,4 +170,15 @@ public final class HotwordMetricsLogger { return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__NORMAL_DETECTOR; } } + + private static int getAudioEgressDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return AUDIO_EGRESS_SOFTWARE_DETECTOR; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return AUDIO_EGRESS_DSP_DETECTOR; + default: + return AUDIO_EGRESS_NORMAL_DETECTOR; + } + } } |