summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp3
-rw-r--r--packages/SettingsLib/res/drawable/ic_bt_le_audio.xml14
-rw-r--r--packages/SettingsLib/res/drawable/ic_bt_le_audio_speakers.xml31
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java15
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java45
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt6
-rw-r--r--packages/SystemUI/res/values/flags.xml3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt114
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt162
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt166
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt99
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt73
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java71
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt194
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt105
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt144
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt140
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt210
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt33
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java2
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java45
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java53
-rw-r--r--services/core/java/com/android/server/wm/Task.java14
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java30
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java68
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java75
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java105
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java13
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java104
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java30
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;
+ }
+ }
}