summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt2
-rw-r--r--core/java/android/app/Notification.java81
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java484
-rw-r--r--core/java/android/app/SystemServiceRegistry.java5
-rw-r--r--core/java/android/app/performance.aconfig11
-rw-r--r--core/java/android/content/Intent.java10
-rw-r--r--core/java/android/content/pm/flags.aconfig15
-rw-r--r--core/java/android/content/pm/parsing/ApkLite.java54
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java64
-rw-r--r--core/java/android/content/pm/parsing/PackageLite.java28
-rw-r--r--core/java/android/hardware/input/AidlKeyGestureEvent.aidl6
-rw-r--r--core/java/android/hardware/input/AppLaunchData.java176
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java150
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java4
-rw-r--r--core/java/android/os/Build.java12
-rw-r--r--core/java/android/window/BackEvent.java5
-rw-r--r--core/java/com/android/internal/os/ApplicationSharedMemory.java31
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java289
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressModel.java4
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_app_PropertyInvalidatedCache.cpp119
-rw-r--r--core/jni/android_app_PropertyInvalidatedCache.h146
-rw-r--r--core/jni/com_android_internal_os_ApplicationSharedMemory.cpp24
-rw-r--r--core/res/res/values/attrs.xml6
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java377
-rw-r--r--core/tests/coretests/Android.bp4
-rw-r--r--core/tests/coretests/AndroidTest.xml12
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java85
-rw-r--r--core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java63
-rw-r--r--core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java301
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java54
-rw-r--r--libs/WindowManager/Shell/Android.bp7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java86
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt16
-rw-r--r--lint-baseline.xml13
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig17
-rw-r--r--packages/SystemUI/animation/lib/Android.bp13
-rw-r--r--packages/SystemUI/animation/lib/OWNERS3
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java5
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java6
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java10
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java1
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java26
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt20
-rw-r--r--packages/SystemUI/common/Android.bp9
-rw-r--r--packages/SystemUI/common/AndroidManifest.xml21
-rw-r--r--packages/SystemUI/common/README.md8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt61
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt37
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt97
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt13
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt52
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt364
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt34
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt30
-rw-r--r--packages/SystemUI/log/Android.bp6
-rw-r--r--packages/SystemUI/log/AndroidManifest.xml22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java68
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt85
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt108
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt5
-rw-r--r--packages/SystemUI/plugin_core/AndroidManifest.xml24
-rw-r--r--packages/SystemUI/res/drawable/notif_footer_btn_history.xml10
-rw-r--r--packages/SystemUI/res/drawable/notif_footer_btn_settings.xml10
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml82
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/res/values/styles.xml10
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java24
-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/keyguard/data/repository/FakeKeyguardTransitionRepository.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt2
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java14
-rwxr-xr-xravenwood/scripts/ravenwood-test-summary75
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java15
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java28
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java152
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java4
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java63
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java1
-rw-r--r--services/core/lint-baseline.xml673
-rw-r--r--services/java/com/android/server/SystemServer.java11
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java21
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java26
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java192
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java48
159 files changed, 5070 insertions, 1321 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index aa6615b69f50..46a864e19865 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32852,6 +32852,7 @@ package android.os {
public static class Build.VERSION_CODES {
ctor public Build.VERSION_CODES();
+ field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int BAKLAVA = 10000; // 0x2710
field public static final int BASE = 1; // 0x1
field public static final int BASE_1_1 = 2; // 0x2
field public static final int CUPCAKE = 3; // 0x3
@@ -32891,6 +32892,7 @@ package android.os {
}
@FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL {
+ field public static final int BAKLAVA = 1000000000; // 0x3b9aca00
field public static final int BASE = 100000; // 0x186a0
field public static final int BASE_1_1 = 200000; // 0x30d40
field public static final int CUPCAKE = 300000; // 0x493e0
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 64aa705447aa..c179e38a953d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1870,7 +1870,7 @@ public class Notification implements Parcelable
* You can test if a RemoteInput matches these constraints using
* {@link RemoteInput#isDataOnly}.
*/
- private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
+ static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
/**
* No semantic action defined.
@@ -2089,7 +2089,7 @@ public class Notification implements Parcelable
* of non-textual RemoteInputs do not access these remote inputs.
*/
public RemoteInput[] getDataOnlyRemoteInputs() {
- return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
+ return mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
}
/**
@@ -2330,8 +2330,8 @@ public class Notification implements Parcelable
checkContextualActionNullFields();
ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
- RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
- mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
+ RemoteInput[] previousDataInputs =
+ mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
if (previousDataInputs != null) {
for (RemoteInput input : previousDataInputs) {
dataOnlyInputs.add(input);
@@ -3091,7 +3091,8 @@ public class Notification implements Parcelable
visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
}
- ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
+ ArrayList<Person> people =
+ extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
if (people != null && !people.isEmpty()) {
for (Person p : people) {
p.visitUris(visitor);
@@ -3117,8 +3118,8 @@ public class Notification implements Parcelable
person.visitUris(visitor);
}
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
- Parcelable.class);
+ final Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
+ Bundle.class);
if (!ArrayUtils.isEmpty(messages)) {
for (MessagingStyle.Message message : MessagingStyle.Message
.getMessagesFromBundleArray(messages)) {
@@ -3126,8 +3127,8 @@ public class Notification implements Parcelable
}
}
- final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
- Parcelable.class);
+ final Bundle[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
+ Bundle.class);
if (!ArrayUtils.isEmpty(historic)) {
for (MessagingStyle.Message message : MessagingStyle.Message
.getMessagesFromBundleArray(historic)) {
@@ -3838,17 +3839,9 @@ public class Notification implements Parcelable
*/
private void fixDuplicateExtras() {
if (extras != null) {
- fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
- }
- }
-
- /**
- * If we find an extra that's exactly the same as one of the "real" fields but refers to a
- * separate object, replace it with the field's version to avoid holding duplicate copies.
- */
- private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
- if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) {
- extras.putParcelable(extraName, original);
+ if (mLargeIcon != null) {
+ extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+ }
}
}
@@ -6622,8 +6615,8 @@ public class Notification implements Parcelable
big.setViewVisibility(R.id.actions_container, View.GONE);
}
- RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
- mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
+ RemoteInputHistoryItem[] replyText = mN.extras.getParcelableArray(
+ EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
if (validRemoteInput && replyText != null && replyText.length > 0
&& !TextUtils.isEmpty(replyText[0].getText())
&& p.maxRemoteInputHistory > 0) {
@@ -8027,8 +8020,7 @@ public class Notification implements Parcelable
*/
public boolean hasImage() {
if (isStyle(MessagingStyle.class) && extras != null) {
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
- Parcelable.class);
+ final Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Bundle.class);
if (!ArrayUtils.isEmpty(messages)) {
for (MessagingStyle.Message m : MessagingStyle.Message
.getMessagesFromBundleArray(messages)) {
@@ -9348,10 +9340,10 @@ public class Notification implements Parcelable
mUser = user;
}
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
- Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
+ Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Bundle.class);
mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
- Parcelable.class);
+ Bundle.class);
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
@@ -10218,8 +10210,8 @@ public class Notification implements Parcelable
if (mBuilder.mActions.size() > 0) {
maxRows--;
}
- RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
- mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ RemoteInputHistoryItem[] remoteInputHistory = mBuilder.mN.extras.getParcelableArray(
+ EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
RemoteInputHistoryItem.class);
if (remoteInputHistory != null
&& remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
@@ -13070,7 +13062,8 @@ public class Notification implements Parcelable
public WearableExtender(Notification notif) {
Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
if (wearableBundle != null) {
- List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class);
+ List<Action> actions = wearableBundle.getParcelableArrayList(
+ KEY_ACTIONS, Notification.Action.class);
if (actions != null) {
mActions.addAll(actions);
}
@@ -13079,8 +13072,8 @@ public class Notification implements Parcelable
mDisplayIntent = wearableBundle.getParcelable(
KEY_DISPLAY_INTENT, PendingIntent.class);
- Notification[] pages = getParcelableArrayFromBundle(
- wearableBundle, KEY_PAGES, Notification.class);
+ Notification[] pages =
+ wearableBundle.getParcelableArray(KEY_PAGES, Notification.class);
if (pages != null) {
Collections.addAll(mPages, pages);
}
@@ -14015,7 +14008,7 @@ public class Notification implements Parcelable
if (mParticipants != null && mParticipants.length > 1) {
author = mParticipants[0];
}
- Parcelable[] messages = new Parcelable[mMessages.length];
+ Bundle[] messages = new Bundle[mMessages.length];
for (int i = 0; i < messages.length; i++) {
Bundle m = new Bundle();
m.putString(KEY_TEXT, mMessages[i]);
@@ -14037,8 +14030,7 @@ public class Notification implements Parcelable
if (b == null) {
return null;
}
- Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
- Parcelable.class);
+ Bundle[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, Bundle.class);
String[] messages = null;
if (parcelableMessages != null) {
String[] tmp = new String[parcelableMessages.length];
@@ -14402,27 +14394,6 @@ public class Notification implements Parcelable
}
}
- /**
- * Get an array of Parcelable objects from a parcelable array bundle field.
- * Update the bundle to have a typed array so fetches in the future don't need
- * to do an array copy.
- */
- @Nullable
- private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
- Bundle bundle, String key, Class<T> itemClass) {
- final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
- final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
- if (arrayClass.isInstance(array) || array == null) {
- return (T[]) array;
- }
- final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
- for (int i = 0; i < array.length; i++) {
- typedArray[i] = (T) array[i];
- }
- bundle.putParcelableArray(key, typedArray);
- return typedArray;
- }
-
private static class BuilderRemoteViews extends RemoteViews {
public BuilderRemoteViews(Parcel parcel) {
super(parcel);
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index c93a6dd8969e..bc9e709420f1 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -30,16 +30,23 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ApplicationSharedMemory;
import com.android.internal.os.BackgroundThread;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -203,19 +210,14 @@ public class PropertyInvalidatedCache<Query, Result> {
};
/**
- * Verify that the property name conforms to the standard. Log a warning if this is not true.
- * Note that this is done once in the cache constructor; it does not have to be very fast.
+ * Verify that the property name conforms to the standard and throw if this is not true. Note
+ * that this is done only once for a given property name; it does not have to be very fast.
*/
- private void validateCacheKey(String name) {
- if (Build.IS_USER) {
- // Do not bother checking keys in user builds. The keys will have been tested in
- // eng/userdebug builds already.
- return;
- }
+ private static void throwIfInvalidCacheKey(String name) {
for (int i = 0; i < sValidKeyPrefix.length; i++) {
if (name.startsWith(sValidKeyPrefix[i])) return;
}
- Log.w(TAG, "invalid cache name: " + name);
+ throw new IllegalArgumentException("invalid cache name: " + name);
}
/**
@@ -234,7 +236,8 @@ public class PropertyInvalidatedCache<Query, Result> {
* reserved values cause the cache to be skipped.
*/
// This is the initial value of all cache keys. It is changed when a cache is invalidated.
- private static final int NONCE_UNSET = 0;
+ @VisibleForTesting
+ static final int NONCE_UNSET = 0;
// This value is used in two ways. First, it is used internally to indicate that the cache is
// disabled for the current query. Secondly, it is used to globally disable the cache across
// the entire system. Once a cache is disabled, there is no way to enable it again. The
@@ -685,6 +688,77 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
+ * Manage nonces that are stored in shared memory.
+ */
+ private static final class NonceSharedMem extends NonceHandler {
+ // The shared memory.
+ private volatile NonceStore mStore;
+
+ // The index of the nonce in shared memory.
+ private volatile int mHandle = NonceStore.INVALID_NONCE_INDEX;
+
+ // True if the string has been stored, ever.
+ private volatile boolean mRecorded = false;
+
+ // A short name that is saved in shared memory. This is the portion of the property name
+ // that follows the prefix.
+ private final String mShortName;
+
+ NonceSharedMem(@NonNull String name, @Nullable String prefix) {
+ super(name);
+ if ((prefix != null) && name.startsWith(prefix)) {
+ mShortName = name.substring(prefix.length());
+ } else {
+ mShortName = name;
+ }
+ }
+
+ // Fetch the nonce from shared memory. If the shared memory is not available, return
+ // UNSET. If the shared memory is available but the nonce name is not known (it may not
+ // have been invalidated by the server yet), return UNSET.
+ @Override
+ long getNonceInternal() {
+ if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+ if (mStore == null) {
+ mStore = NonceStore.getInstance();
+ if (mStore == null) {
+ return NONCE_UNSET;
+ }
+ }
+ mHandle = mStore.getHandleForName(mShortName);
+ if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+ return NONCE_UNSET;
+ }
+ }
+ return mStore.getNonce(mHandle);
+ }
+
+ // Set the nonce in shared mmory. If the shared memory is not available, throw an
+ // exception. Otherwise, if the nonce name has never been recorded, record it now and
+ // fetch the handle for the name. If the handle cannot be created, throw an exception.
+ @Override
+ void setNonceInternal(long value) {
+ if (mHandle == NonceStore.INVALID_NONCE_INDEX || !mRecorded) {
+ if (mStore == null) {
+ mStore = NonceStore.getInstance();
+ if (mStore == null) {
+ throw new IllegalStateException("setNonce: shared memory not ready");
+ }
+ }
+ // Always store the name before fetching the handle. storeName() is idempotent
+ // but does take a little time, so this code calls it just once.
+ mStore.storeName(mShortName);
+ mRecorded = true;
+ mHandle = mStore.getHandleForName(mShortName);
+ if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+ throw new IllegalStateException("setNonce: shared memory store failed");
+ }
+ }
+ mStore.setNonce(mHandle, value);
+ }
+ }
+
+ /**
* SystemProperties and shared storage are protected and cannot be written by random
* processes. So, for testing purposes, the NonceLocal handler stores the nonce locally. The
* NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
@@ -712,6 +786,7 @@ public class PropertyInvalidatedCache<Query, Result> {
* Complete key prefixes.
*/
private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
+ private static final String PREFIX_SYSTEM = CACHE_KEY_PREFIX + "." + MODULE_SYSTEM + ".";
/**
* A static list of nonce handlers, indexed by name. NonceHandlers can be safely shared by
@@ -722,16 +797,32 @@ public class PropertyInvalidatedCache<Query, Result> {
private static final ConcurrentHashMap<String, NonceHandler> sHandlers
= new ConcurrentHashMap<>();
+ // True if shared memory is flag-enabled, false otherwise. Read the flags exactly once.
+ private static final boolean sSharedMemoryAvailable =
+ com.android.internal.os.Flags.applicationSharedMemoryEnabled()
+ && android.app.Flags.picUsesSharedMemory();
+
+ // Return true if this cache can use shared memory for its nonce. Shared memory may be used
+ // if the module is the system.
+ private static boolean sharedMemoryOkay(@NonNull String name) {
+ return sSharedMemoryAvailable && name.startsWith(PREFIX_SYSTEM);
+ }
+
/**
- * Return the proper nonce handler, based on the property name.
+ * Return the proper nonce handler, based on the property name. A handler is created if
+ * necessary. Before a handler is created, the name is checked, and an exception is thrown if
+ * the name is not valid.
*/
private static NonceHandler getNonceHandler(@NonNull String name) {
NonceHandler h = sHandlers.get(name);
if (h == null) {
synchronized (sGlobalLock) {
+ throwIfInvalidCacheKey(name);
h = sHandlers.get(name);
if (h == null) {
- if (name.startsWith(PREFIX_TEST)) {
+ if (sharedMemoryOkay(name)) {
+ h = new NonceSharedMem(name, PREFIX_SYSTEM);
+ } else if (name.startsWith(PREFIX_TEST)) {
h = new NonceLocal(name);
} else {
h = new NonceSysprop(name);
@@ -774,7 +865,6 @@ public class PropertyInvalidatedCache<Query, Result> {
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
mPropertyName = propertyName;
- validateCacheKey(mPropertyName);
mCacheName = cacheName;
mNonce = getNonceHandler(mPropertyName);
mMaxEntries = maxEntries;
@@ -799,7 +889,6 @@ public class PropertyInvalidatedCache<Query, Result> {
public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
mPropertyName = createPropertyName(module, api);
- validateCacheKey(mPropertyName);
mCacheName = cacheName;
mNonce = getNonceHandler(mPropertyName);
mMaxEntries = maxEntries;
@@ -1620,6 +1709,14 @@ public class PropertyInvalidatedCache<Query, Result> {
// then only that cache is reported.
boolean detail = anyDetailed(args);
+ if (sSharedMemoryAvailable) {
+ pw.println(" SharedMemory: enabled");
+ NonceStore.getInstance().dump(pw, " ", detail);
+ } else {
+ pw.println(" SharedMemory: disabled");
+ }
+ pw.println();
+
ArrayList<PropertyInvalidatedCache> activeCaches = getActiveCaches();
for (int i = 0; i < activeCaches.size(); i++) {
PropertyInvalidatedCache currentCache = activeCaches.get(i);
@@ -1654,4 +1751,363 @@ public class PropertyInvalidatedCache<Query, Result> {
Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
}
}
+
+ /**
+ * Nonces in shared memory are supported by a string block that acts as a table of contents
+ * for nonce names, and an array of nonce values. There are two key design principles with
+ * respect to nonce maps:
+ *
+ * 1. It is always okay if a nonce value cannot be determined. If the nonce is UNSET, the
+ * cache is bypassed, which is always functionally correct. Clients do not take extraordinary
+ * measures to be current with the nonce map. Clients must be current with the nonce itself;
+ * this is achieved through the shared memory.
+ *
+ * 2. Once a name is mapped to a nonce index, the mapping is fixed for the lifetime of the
+ * system. It is only necessary to distinguish between the unmapped and mapped states. Once
+ * a client has mapped a nonce, that mapping is known to be good for the lifetime of the
+ * system.
+ * @hide
+ */
+ @VisibleForTesting
+ public static class NonceStore {
+
+ // A lock for the store.
+ private final Object mLock = new Object();
+
+ // The native pointer. This is not owned by this class. It is owned by
+ // ApplicationSharedMemory, and it disappears when the owning instance is closed.
+ private final long mPtr;
+
+ // True if the memory is immutable.
+ private final boolean mMutable;
+
+ // The maximum length of a string in the string block. The maximum length must fit in a
+ // byte, but a smaller value has been chosen to limit memory use. Because strings are
+ // run-length encoded, a string consumes at most MAX_STRING_LENGTH+1 bytes in the string
+ // block.
+ private static final int MAX_STRING_LENGTH = 63;
+
+ // The raw byte block. Strings are stored as run-length encoded byte arrays. The first
+ // byte is the length of the following string. It is an axiom of the system that the
+ // string block is initially all zeros and that it is write-once memory: new strings are
+ // appended to existing strings, so there is never a need to revisit strings that have
+ // already been pulled from the string block.
+ @GuardedBy("mLock")
+ private final byte[] mStringBlock;
+
+ // The expected hash code of the string block. If the hash over the string block equals
+ // this value, then the string block is valid. Otherwise, the block is not valid and
+ // should be re-read. An invalid block generally means that a client has read the shared
+ // memory while the server was still writing it.
+ @GuardedBy("mLock")
+ private int mBlockHash = 0;
+
+ // The number of nonces that the native layer can hold. This is maintained for debug and
+ // logging.
+ private final int mMaxNonce;
+
+ /** @hide */
+ @VisibleForTesting
+ public NonceStore(long ptr, boolean mutable) {
+ mPtr = ptr;
+ mMutable = mutable;
+ mStringBlock = new byte[nativeGetMaxByte(ptr)];
+ mMaxNonce = nativeGetMaxNonce(ptr);
+ refreshStringBlockLocked();
+ }
+
+ // The static lock for singleton acquisition.
+ private static Object sLock = new Object();
+
+ // NonceStore is supposed to be a singleton.
+ private static NonceStore sInstance;
+
+ // Return the singleton instance.
+ static NonceStore getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ try {
+ ApplicationSharedMemory shmem = ApplicationSharedMemory.getInstance();
+ sInstance = (shmem == null)
+ ? null
+ : new NonceStore(shmem.getSystemNonceBlock(),
+ shmem.isMutable());
+ } catch (IllegalStateException e) {
+ // ApplicationSharedMemory.getInstance() throws if the shared memory is
+ // not yet mapped. Swallow the exception and leave sInstance null.
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ // The index value of an unmapped name.
+ public static final int INVALID_NONCE_INDEX = -1;
+
+ // The highest string index extracted from the string block. -1 means no strings have
+ // been seen. This is used to skip strings that have already been processed, when the
+ // string block is updated.
+ @GuardedBy("mLock")
+ private int mHighestIndex = -1;
+
+ // The number bytes of the string block that has been used. This is a statistics.
+ @GuardedBy("mLock")
+ private int mStringBytes = 0;
+
+ // The number of partial reads on the string block. This is a statistic.
+ @GuardedBy("mLock")
+ private int mPartialReads = 0;
+
+ // The number of times the string block was updated. This is a statistic.
+ @GuardedBy("mLock")
+ private int mStringUpdated = 0;
+
+ // Map a string to a native index.
+ @GuardedBy("mLock")
+ private final ArrayMap<String, Integer> mStringHandle = new ArrayMap<>();
+
+ // Update the string map from the current string block. The string block is not modified
+ // and the block hash is not checked. The function skips past strings that have already
+ // been read, and then processes any new strings.
+ @GuardedBy("mLock")
+ private void updateStringMapLocked() {
+ int index = 0;
+ int offset = 0;
+ while (offset < mStringBlock.length && mStringBlock[offset] != 0) {
+ if (index > mHighestIndex) {
+ // Only record the string if it has not been seen yet.
+ final String s = new String(mStringBlock, offset+1, mStringBlock[offset]);
+ mStringHandle.put(s, index);
+ mHighestIndex = index;
+ }
+ offset += mStringBlock[offset] + 1;
+ index++;
+ }
+ mStringBytes = offset;
+ }
+
+ // Append a string to the string block and update the hash. This does not write the block
+ // to shared memory.
+ @GuardedBy("mLock")
+ private void appendStringToMapLocked(@NonNull String str) {
+ int offset = 0;
+ while (offset < mStringBlock.length && mStringBlock[offset] != 0) {
+ offset += mStringBlock[offset] + 1;
+ }
+ final byte[] strBytes = str.getBytes();
+
+ if (offset + strBytes.length >= mStringBlock.length) {
+ // Overflow. Do not add the string to the block; the string will remain undefined.
+ return;
+ }
+
+ mStringBlock[offset] = (byte) strBytes.length;
+ offset++;
+ for (int i = 0; i < strBytes.length; i++, offset++) {
+ mStringBlock[offset] = strBytes[i];
+ }
+ mBlockHash = Arrays.hashCode(mStringBlock);
+ }
+
+ // Possibly update the string block. If the native shared memory has a new block hash,
+ // then read the new string block values from shared memory, as well as the new hash.
+ @GuardedBy("mLock")
+ private void refreshStringBlockLocked() {
+ if (mBlockHash == nativeGetByteBlockHash(mPtr)) {
+ // The fastest way to know that the shared memory string block has not changed.
+ return;
+ }
+ final int hash = nativeGetByteBlock(mPtr, mBlockHash, mStringBlock);
+ if (hash != Arrays.hashCode(mStringBlock)) {
+ // This is a partial read: ignore it. The next time someone needs this string
+ // the memory will be read again and should succeed. Set the local hash to
+ // zero to ensure that the next read attempt will actually read from shared
+ // memory.
+ mBlockHash = 0;
+ mPartialReads++;
+ return;
+ }
+ // The hash has changed. Update the strings from the byte block.
+ mStringUpdated++;
+ mBlockHash = hash;
+ updateStringMapLocked();
+ }
+
+ // Throw an exception if the string cannot be stored in the string block.
+ private static void throwIfBadString(@NonNull String s) {
+ if (s.length() == 0) {
+ throw new IllegalArgumentException("cannot store an empty string");
+ }
+ if (s.length() > MAX_STRING_LENGTH) {
+ throw new IllegalArgumentException("cannot store a string longer than "
+ + MAX_STRING_LENGTH);
+ }
+ }
+
+ // Throw an exception if the nonce handle is invalid. The handle is bad if it is out of
+ // range of allocated handles. Note that NONCE_HANDLE_INVALID will throw: this is
+ // important for setNonce().
+ @GuardedBy("mLock")
+ private void throwIfBadHandle(int handle) {
+ if (handle < 0 || handle > mHighestIndex) {
+ throw new IllegalArgumentException("invalid nonce handle: " + handle);
+ }
+ }
+
+ // Throw if the memory is immutable (the process does not have write permission). The
+ // exception mimics the permission-denied exception thrown when a process writes to an
+ // unauthorized system property.
+ private void throwIfImmutable() {
+ if (!mMutable) {
+ throw new RuntimeException("write permission denied");
+ }
+ }
+
+ // Add a string to the local copy of the block and write the block to shared memory.
+ // Return the index of the new string. If the string has already been recorded, the
+ // shared memory is not updated but the index of the existing string is returned.
+ public int storeName(@NonNull String str) {
+ synchronized (mLock) {
+ Integer handle = mStringHandle.get(str);
+ if (handle == null) {
+ throwIfImmutable();
+ throwIfBadString(str);
+ appendStringToMapLocked(str);
+ nativeSetByteBlock(mPtr, mBlockHash, mStringBlock);
+ updateStringMapLocked();
+ handle = mStringHandle.get(str);
+ }
+ return handle;
+ }
+ }
+
+ // Retrieve the handle for a string. -1 is returned if the string is not found.
+ public int getHandleForName(@NonNull String str) {
+ synchronized (mLock) {
+ Integer handle = mStringHandle.get(str);
+ if (handle == null) {
+ refreshStringBlockLocked();
+ handle = mStringHandle.get(str);
+ }
+ return (handle != null) ? handle : INVALID_NONCE_INDEX;
+ }
+ }
+
+ // Thin wrapper around the native method.
+ public boolean setNonce(int handle, long value) {
+ synchronized (mLock) {
+ throwIfBadHandle(handle);
+ throwIfImmutable();
+ return nativeSetNonce(mPtr, handle, value);
+ }
+ }
+
+ public long getNonce(int handle) {
+ synchronized (mLock) {
+ throwIfBadHandle(handle);
+ return nativeGetNonce(mPtr, handle);
+ }
+ }
+
+ /**
+ * Dump the nonce statistics
+ */
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix, boolean detailed) {
+ synchronized (mLock) {
+ pw.println(formatSimple(
+ "%sStringsMapped: %d, BytesUsed: %d",
+ prefix, mHighestIndex, mStringBytes));
+ pw.println(formatSimple(
+ "%sPartialReads: %d, StringUpdates: %d",
+ prefix, mPartialReads, mStringUpdated));
+
+ if (detailed) {
+ for (String s: mStringHandle.keySet()) {
+ int h = mStringHandle.get(s);
+ pw.println(formatSimple(
+ "%sHandle:%d Name:%s", prefix, h, s));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the maximum number of nonces supported in the native layer.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @return the number of nonces supported by the shared memory.
+ */
+ private static native int nativeGetMaxNonce(long mPtr);
+
+ /**
+ * Return the maximum number of string bytes supported in the native layer.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @return the number of string bytes supported by the shared memory.
+ */
+ private static native int nativeGetMaxByte(long mPtr);
+
+ /**
+ * Write the byte block and set the hash into shared memory. The method is relatively
+ * forgiving, in that any non-null byte array will be stored without error. The number of
+ * bytes will the lesser of the length of the block parameter and the size of the native
+ * array. The native layer performs no checks on either byte block or the hash.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @param hash a value to be stored in the native block hash.
+ * @param block the byte array to be store.
+ */
+ @FastNative
+ private static native void nativeSetByteBlock(long mPtr, int hash, @NonNull byte[] block);
+
+ /**
+ * Retrieve the string block into the array and return the hash value. If the incoming hash
+ * value is the same as the hash in shared memory, the native function returns immediately
+ * without touching the block parameter. Note that a zero hash value will always cause shared
+ * memory to be read. The number of bytes read is the lesser of the length of the block
+ * parameter and the size of the native array.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @param hash a value to be compared against the hash in the native layer.
+ * @param block an array to receive the bytes from the native layer.
+ * @return the hash from the native layer.
+ */
+ @FastNative
+ private static native int nativeGetByteBlock(long mPtr, int hash, @NonNull byte[] block);
+
+ /**
+ * Retrieve just the byte block hash from the native layer. The function is CriticalNative
+ * and thus very fast.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @return the current native hash value.
+ */
+ @CriticalNative
+ private static native int nativeGetByteBlockHash(long mPtr);
+
+ /**
+ * Set a nonce at the specified index. The index is checked against the size of the native
+ * nonce array and the function returns true if the index is valid, and false. The function
+ * is CriticalNative and thus very fast.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @param index the index of the nonce to set.
+ * @param value the value to set for the nonce.
+ * @return true if the index is inside the nonce array and false otherwise.
+ */
+ @CriticalNative
+ private static native boolean nativeSetNonce(long mPtr, int index, long value);
+
+ /**
+ * Get the nonce from the specified index. The index is checked against the size of the
+ * native nonce array; the function returns the nonce value if the index is valid, and 0
+ * otherwise. The function is CriticalNative and thus very fast.
+ *
+ * @param mPtr the pointer to the native shared memory.
+ * @param index the index of the nonce to retrieve.
+ * @return the value of the specified nonce, of 0 if the index is out of bounds.
+ */
+ @CriticalNative
+ private static native long nativeGetNonce(long mPtr, int index);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bd26db55052b..c6b8f3baecf6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1750,10 +1750,13 @@ public final class SystemServiceRegistry {
@Override
public AdvancedProtectionManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
- IBinder iBinder = ServiceManager.getServiceOrThrow(
+ IBinder iBinder = ServiceManager.getService(
Context.ADVANCED_PROTECTION_SERVICE);
IAdvancedProtectionService service =
IAdvancedProtectionService.Stub.asInterface(iBinder);
+ if (service == null) {
+ return null;
+ }
return new AdvancedProtectionManager(service);
}
});
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
new file mode 100644
index 000000000000..7c6989e4f3e9
--- /dev/null
+++ b/core/java/android/app/performance.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+container: "system"
+
+flag {
+ namespace: "system_performance"
+ name: "pic_uses_shared_memory"
+ is_exported: true
+ is_fixed_read_only: true
+ description: "PropertyInvalidatedCache uses shared memory for nonces."
+ bug: "366552454"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index aa0e1c200df5..66ef004c5298 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11672,6 +11672,7 @@ public class Intent implements Parcelable, Cloneable {
Log.w(TAG, "Failure filling in extras", e);
}
}
+ mCreatorTokenInfo = other.mCreatorTokenInfo;
if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
&& other.mContentUserHint != UserHandle.USER_CURRENT) {
mContentUserHint = other.mContentUserHint;
@@ -12225,6 +12226,13 @@ public class Intent implements Parcelable, Cloneable {
}
/** @hide */
+ public void removeCreatorToken() {
+ if (mCreatorTokenInfo != null) {
+ mCreatorTokenInfo.mCreatorToken = null;
+ }
+ }
+
+ /** @hide */
public @Nullable IBinder getCreatorToken() {
return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mCreatorToken;
}
@@ -12251,7 +12259,7 @@ public class Intent implements Parcelable, Cloneable {
public void collectExtraIntentKeys() {
if (!isPreventIntentRedirectEnabled()) return;
- if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+ if (mExtras != null && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
if (mExtras.get(key) instanceof Intent) {
if (mCreatorTokenInfo == null) {
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5f439b1dcab9..6f70586881be 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -297,14 +297,6 @@ flag {
}
flag {
- name: "get_packages_from_launcher_apps"
- namespace: "package_manager_service"
- description: "Feature flag to provide the new methods within launcher apps class to get packages."
- bug: "363324203"
- is_fixed_read_only: true
-}
-
-flag {
name: "remove_cross_user_permission_hack"
namespace: "package_manager_service"
description: "Feature flag to remove hack code of using PackageManager.MATCH_ANY_USER flag without cross user permission."
@@ -328,6 +320,13 @@ flag {
}
flag {
+ name: "sdk_dependency_installer"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable installation of missing sdk dependency of app"
+ bug: "370822870"
+}
+
+flag {
name: "include_feature_flags_in_package_cacher"
namespace: "package_manager_service"
description: "Include feature flag status when determining hits or misses in PackageCacher."
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 19a13db15b05..4220590a6943 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -28,6 +28,7 @@ import android.content.pm.VerifierInfo;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -141,6 +142,21 @@ public class ApkLite {
private final boolean mIsSdkLibrary;
/**
+ * List of SDK names used by this apk.
+ */
+ private final @NonNull List<String> mUsesSdkLibraries;
+
+ /**
+ * List of SDK major versions used by this apk.
+ */
+ private final @Nullable long[] mUsesSdkLibrariesVersionsMajor;
+
+ /**
+ * List of SDK certificates used by this apk.
+ */
+ private final @Nullable String[][] mUsesSdkLibrariesCertDigests;
+
+ /**
* Indicates if this system app can be updated.
*/
private final boolean mUpdatableSystem;
@@ -167,7 +183,9 @@ public class ApkLite {
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary,
+ List<String> usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
+ String[][] usesSdkLibrariesCertDigests, boolean updatableSystem,
String emergencyInstaller, List<SharedLibraryInfo> declaredLibraries) {
mPath = path;
mPackageName = packageName;
@@ -202,6 +220,9 @@ public class ApkLite {
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
+ mUsesSdkLibraries = usesSdkLibraries;
+ mUsesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
+ mUsesSdkLibrariesCertDigests = usesSdkLibrariesCertDigests;
mUpdatableSystem = updatableSystem;
mEmergencyInstaller = emergencyInstaller;
mArchivedPackage = null;
@@ -242,6 +263,9 @@ public class ApkLite {
mRollbackDataPolicy = 0;
mHasDeviceAdminReceiver = false;
mIsSdkLibrary = false;
+ mUsesSdkLibraries = Collections.emptyList();
+ mUsesSdkLibrariesVersionsMajor = null;
+ mUsesSdkLibrariesCertDigests = null;
mUpdatableSystem = true;
mEmergencyInstaller = null;
mArchivedPackage = archivedPackage;
@@ -555,6 +579,30 @@ public class ApkLite {
}
/**
+ * List of SDK names used by this apk.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<String> getUsesSdkLibraries() {
+ return mUsesSdkLibraries;
+ }
+
+ /**
+ * List of SDK major versions used by this apk.
+ */
+ @DataClass.Generated.Member
+ public @Nullable long[] getUsesSdkLibrariesVersionsMajor() {
+ return mUsesSdkLibrariesVersionsMajor;
+ }
+
+ /**
+ * List of SDK certificates used by this apk.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String[][] getUsesSdkLibrariesCertDigests() {
+ return mUsesSdkLibrariesCertDigests;
+ }
+
+ /**
* Indicates if this system app can be updated.
*/
@DataClass.Generated.Member
@@ -584,10 +632,10 @@ public class ApkLite {
}
@DataClass.Generated(
- time = 1728333566322L,
+ time = 1729247366948L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 1a7f628ae61c..50d875845b2f 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -32,6 +32,7 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.res.ApkAssets;
import android.content.res.XmlResourceParser;
import android.os.Build;
+import android.os.SystemProperties;
import android.os.Trace;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -44,6 +45,7 @@ import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
+import libcore.util.HexEncoding;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -88,6 +90,7 @@ public class ApkLiteParseUtils {
private static final String TAG_USES_SDK = "uses-sdk";
private static final String TAG_USES_SPLIT = "uses-split";
private static final String TAG_MANIFEST = "manifest";
+ private static final String TAG_USES_SDK_LIBRARY = "uses-sdk-library";
private static final String TAG_SDK_LIBRARY = "sdk-library";
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
@@ -460,6 +463,9 @@ public class ApkLiteParseUtils {
boolean hasDeviceAdminReceiver = false;
boolean isSdkLibrary = false;
+ List<String> usesSdkLibraries = new ArrayList<>();
+ long[] usesSdkLibrariesVersionsMajor = new long[0];
+ String[][] usesSdkLibrariesCertDigests = new String[0][0];
List<SharedLibraryInfo> declaredLibraries = new ArrayList<>();
// Only search the tree when the tag is the direct child of <manifest> tag
@@ -523,6 +529,57 @@ public class ApkLiteParseUtils {
hasDeviceAdminReceiver |= isDeviceAdminReceiver(parser,
hasBindDeviceAdminPermission);
break;
+ case TAG_USES_SDK_LIBRARY:
+ String usesSdkLibName = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "name");
+ long usesSdkLibVersionMajor = parser.getAttributeIntValue(
+ ANDROID_RES_NAMESPACE, "versionMajor", -1);
+ String usesSdkCertDigest = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "certDigest");
+
+ if (usesSdkLibName == null || usesSdkLibName.isBlank()
+ || usesSdkLibVersionMajor < 0) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Bad uses-sdk-library declaration name: "
+ + usesSdkLibName
+ + " version: " + usesSdkLibVersionMajor);
+ }
+
+ if (usesSdkLibraries.contains(usesSdkLibName)) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Bad uses-sdk-library declaration. Depending on"
+ + " multiple versions of SDK library: "
+ + usesSdkLibName);
+ }
+
+ usesSdkLibraries.add(usesSdkLibName);
+ usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
+ usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
+ /*allowDuplicates=*/ true);
+
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ // TODO(372862145): Add test for this replacement
+ usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase();
+
+ if ("".equals(usesSdkCertDigest)) {
+ // Test-only uses-sdk-library empty certificate digest override.
+ usesSdkCertDigest = SystemProperties.get(
+ "debug.pm.uses_sdk_library_default_cert_digest", "");
+ // Validate the overridden digest.
+ try {
+ HexEncoding.decode(usesSdkCertDigest, false);
+ } catch (IllegalArgumentException e) {
+ usesSdkCertDigest = "";
+ }
+ }
+ // TODO(372862145): Add support for multiple signer
+ usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
+ usesSdkLibrariesCertDigests, new String[]{usesSdkCertDigest},
+ /*allowDuplicates=*/ true);
+ break;
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
// Mirrors ParsingPackageUtils#parseSdkLibrary until lite and full
@@ -534,7 +591,7 @@ public class ApkLiteParseUtils {
if (sdkLibName == null || sdkLibVersionMajor < 0) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
- "Bad uses-sdk-library declaration name: " + sdkLibName
+ "Bad sdk-library declaration name: " + sdkLibName
+ " version: " + sdkLibVersionMajor);
}
declaredLibraries.add(new SharedLibraryInfo(
@@ -694,8 +751,9 @@ public class ApkLiteParseUtils {
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller,
- declaredLibraries));
+ hasDeviceAdminReceiver, isSdkLibrary, usesSdkLibraries,
+ usesSdkLibrariesVersionsMajor, usesSdkLibrariesCertDigests,
+ updatableSystem, emergencyInstaller, declaredLibraries));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index 9a2ee7fe4cc6..79c597327f5a 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -115,6 +115,12 @@ public class PackageLite {
*/
private final boolean mIsSdkLibrary;
+ private final @NonNull List<String> mUsesSdkLibraries;
+
+ private final @Nullable long[] mUsesSdkLibrariesVersionsMajor;
+
+ private final @Nullable String[][] mUsesSdkLibrariesCertDigests;
+
private final @NonNull List<SharedLibraryInfo> mDeclaredLibraries;
/**
@@ -149,6 +155,9 @@ public class PackageLite {
mSplitRequired = (baseApk.isSplitRequired() || hasAnyRequiredSplitTypes());
mProfileableByShell = baseApk.isProfileableByShell();
mIsSdkLibrary = baseApk.isIsSdkLibrary();
+ mUsesSdkLibraries = baseApk.getUsesSdkLibraries();
+ mUsesSdkLibrariesVersionsMajor = baseApk.getUsesSdkLibrariesVersionsMajor();
+ mUsesSdkLibrariesCertDigests = baseApk.getUsesSdkLibrariesCertDigests();
mSplitNames = splitNames;
mSplitTypes = splitTypes;
mIsFeatureSplits = isFeatureSplits;
@@ -438,6 +447,21 @@ public class PackageLite {
}
@DataClass.Generated.Member
+ public @NonNull List<String> getUsesSdkLibraries() {
+ return mUsesSdkLibraries;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable long[] getUsesSdkLibrariesVersionsMajor() {
+ return mUsesSdkLibrariesVersionsMajor;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String[][] getUsesSdkLibrariesCertDigests() {
+ return mUsesSdkLibrariesCertDigests;
+ }
+
+ @DataClass.Generated.Member
public @NonNull List<SharedLibraryInfo> getDeclaredLibraries() {
return mDeclaredLibraries;
}
@@ -451,10 +475,10 @@ public class PackageLite {
}
@DataClass.Generated(
- time = 1728333569917L,
+ time = 1729248757933L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/input/AidlKeyGestureEvent.aidl b/core/java/android/hardware/input/AidlKeyGestureEvent.aidl
index 7cf8795085e3..fc7151940bd7 100644
--- a/core/java/android/hardware/input/AidlKeyGestureEvent.aidl
+++ b/core/java/android/hardware/input/AidlKeyGestureEvent.aidl
@@ -26,4 +26,10 @@ parcelable AidlKeyGestureEvent {
int action;
int displayId;
int flags;
+
+ // App launch parameters: only set when gestureType = KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+ String appLaunchCategory;
+ String appLaunchRole;
+ String appLaunchPackageName;
+ String appLaunchClassName;
}
diff --git a/core/java/android/hardware/input/AppLaunchData.java b/core/java/android/hardware/input/AppLaunchData.java
new file mode 100644
index 000000000000..43186f5db415
--- /dev/null
+++ b/core/java/android/hardware/input/AppLaunchData.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Provides data for launching an application.
+ *
+ * @hide
+ */
+public interface AppLaunchData {
+
+ /** Creates AppLaunchData for the provided category */
+ @NonNull
+ static AppLaunchData createLaunchDataForCategory(@NonNull String category) {
+ return new CategoryData(category);
+ }
+
+ /** Creates AppLaunchData for the provided role */
+ @NonNull
+ static AppLaunchData createLaunchDataForRole(@NonNull String role) {
+ return new RoleData(role);
+ }
+
+ /** Creates AppLaunchData for the target package name and class name */
+ @NonNull
+ static AppLaunchData createLaunchDataForComponent(@NonNull String packageName,
+ @NonNull String className) {
+ return new ComponentData(packageName, className);
+ }
+
+ @Nullable
+ static AppLaunchData createLaunchData(@Nullable String category, @Nullable String role,
+ @Nullable String packageName, @Nullable String className) {
+ if (!TextUtils.isEmpty(category)) {
+ return new CategoryData(category);
+ }
+ if (!TextUtils.isEmpty(role)) {
+ return new RoleData(role);
+ }
+ if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+ return new ComponentData(packageName, className);
+ }
+ return null;
+ }
+
+ /** Intent category based app launch data */
+ class CategoryData implements AppLaunchData {
+ @NonNull
+ private final String mCategory;
+ public CategoryData(@NonNull String category) {
+ mCategory = category;
+ }
+
+ @NonNull
+ public String getCategory() {
+ return mCategory;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CategoryData that)) return false;
+ return Objects.equals(mCategory, that.mCategory);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCategory);
+ }
+
+ @Override
+ public String toString() {
+ return "CategoryData{" +
+ "mCategory='" + mCategory + '\'' +
+ '}';
+ }
+ }
+
+ /** Role based app launch data */
+ class RoleData implements AppLaunchData {
+ @NonNull
+ private final String mRole;
+ public RoleData(@NonNull String role) {
+ mRole = role;
+ }
+
+ @NonNull
+ public String getRole() {
+ return mRole;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RoleData roleData)) return false;
+ return Objects.equals(mRole, roleData.mRole);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRole);
+ }
+
+ @Override
+ public String toString() {
+ return "RoleData{" +
+ "mRole='" + mRole + '\'' +
+ '}';
+ }
+ }
+
+ /** Target application launch data */
+ class ComponentData implements AppLaunchData {
+ @NonNull
+ private final String mPackageName;
+
+ @NonNull
+ private final String mClassName;
+
+ public ComponentData(@NonNull String packageName, @NonNull String className) {
+ mPackageName = packageName;
+ mClassName = className;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @NonNull
+ public String getClassName() {
+ return mClassName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ComponentData that)) return false;
+ return Objects.equals(mPackageName, that.mPackageName) && Objects.equals(
+ mClassName, that.mClassName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mClassName);
+ }
+
+ @Override
+ public String toString() {
+ return "ComponentData{" +
+ "mPackageName='" + mPackageName + '\'' +
+ ", mClassName='" + mClassName + '\'' +
+ '}';
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 71d17ebc40e3..ee1a6aba2ab5 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -19,6 +19,8 @@ package android.hardware.input;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.role.RoleManager;
+import android.content.Intent;
import android.view.Display;
import android.view.KeyCharacterMap;
@@ -26,6 +28,7 @@ import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* Provides information about the keyboard gesture event being triggered by an external keyboard.
@@ -37,6 +40,9 @@ public final class KeyGestureEvent {
@NonNull
private AidlKeyGestureEvent mKeyGestureEvent;
+ private static final int LOG_EVENT_UNSPECIFIED =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+
public static final int KEY_GESTURE_TYPE_UNSPECIFIED = 0;
public static final int KEY_GESTURE_TYPE_HOME = 1;
public static final int KEY_GESTURE_TYPE_RECENT_APPS = 2;
@@ -76,6 +82,8 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_SLEEP = 36;
public static final int KEY_GESTURE_TYPE_WAKEUP = 37;
public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 38;
+ // TODO(b/280423320): Remove "LAUNCH_DEFAULT_..." gestures and rely on launch intent to find
+ // the correct logging event.
public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 39;
public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 40;
public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 41;
@@ -88,7 +96,7 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 48;
public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 49;
public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 50;
- public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 51;
+ public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION = 51;
public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 52;
public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 53;
public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 54;
@@ -166,7 +174,7 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES,
KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER,
KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
- KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+ KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
KEY_GESTURE_TYPE_DESKTOP_MODE,
KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
@@ -210,6 +218,8 @@ public final class KeyGestureEvent {
private int mAction = KeyGestureEvent.ACTION_GESTURE_COMPLETE;
private int mDisplayId = Display.DEFAULT_DISPLAY;
private int mFlags = 0;
+ @Nullable
+ private AppLaunchData mAppLaunchData = null;
/**
* @see KeyGestureEvent#getDeviceId()
@@ -268,6 +278,14 @@ public final class KeyGestureEvent {
}
/**
+ * @see KeyGestureEvent#getAppLaunchData()
+ */
+ public Builder setAppLaunchData(@NonNull AppLaunchData appLaunchData) {
+ mAppLaunchData = appLaunchData;
+ return this;
+ }
+
+ /**
* Build {@link KeyGestureEvent}
*/
public KeyGestureEvent build() {
@@ -279,6 +297,21 @@ public final class KeyGestureEvent {
event.action = mAction;
event.displayId = mDisplayId;
event.flags = mFlags;
+ if (mAppLaunchData != null) {
+ if (mAppLaunchData instanceof AppLaunchData.CategoryData) {
+ event.appLaunchCategory =
+ ((AppLaunchData.CategoryData) mAppLaunchData).getCategory();
+ } else if (mAppLaunchData instanceof AppLaunchData.RoleData) {
+ event.appLaunchRole = ((AppLaunchData.RoleData) mAppLaunchData).getRole();
+ } else if (mAppLaunchData instanceof AppLaunchData.ComponentData) {
+ event.appLaunchPackageName =
+ ((AppLaunchData.ComponentData) mAppLaunchData).getPackageName();
+ event.appLaunchClassName =
+ ((AppLaunchData.ComponentData) mAppLaunchData).getClassName();
+ } else {
+ throw new IllegalArgumentException("AppLaunchData type is invalid!");
+ }
+ }
return new KeyGestureEvent(event);
}
}
@@ -315,6 +348,27 @@ public final class KeyGestureEvent {
return (mKeyGestureEvent.flags & FLAG_CANCELLED) != 0;
}
+ public int getLogEvent() {
+ if (getKeyGestureType() == KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+ return getLogEventFromLaunchAppData(getAppLaunchData());
+ }
+ return keyGestureTypeToLogEvent(getKeyGestureType());
+ }
+
+ /**
+ * @return Launch app data associated with the event, only if key gesture type is
+ * {@code KEY_GESTURE_TYPE_LAUNCH_APPLICATION}
+ */
+ @Nullable
+ public AppLaunchData getAppLaunchData() {
+ if (mKeyGestureEvent.gestureType != KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+ return null;
+ }
+ return AppLaunchData.createLaunchData(mKeyGestureEvent.appLaunchCategory,
+ mKeyGestureEvent.appLaunchRole, mKeyGestureEvent.appLaunchPackageName,
+ mKeyGestureEvent.appLaunchClassName);
+ }
+
@Override
public String toString() {
return "KeyGestureEvent { "
@@ -324,7 +378,8 @@ public final class KeyGestureEvent {
+ "keyGestureType = " + keyGestureTypeToString(mKeyGestureEvent.gestureType) + ", "
+ "action = " + mKeyGestureEvent.action + ", "
+ "displayId = " + mKeyGestureEvent.displayId + ", "
- + "flags = " + mKeyGestureEvent.flags
+ + "flags = " + mKeyGestureEvent.flags + ", "
+ + "appLaunchData = " + getAppLaunchData()
+ " }";
}
@@ -339,7 +394,11 @@ public final class KeyGestureEvent {
&& mKeyGestureEvent.gestureType == that.mKeyGestureEvent.gestureType
&& mKeyGestureEvent.action == that.mKeyGestureEvent.action
&& mKeyGestureEvent.displayId == that.mKeyGestureEvent.displayId
- && mKeyGestureEvent.flags == that.mKeyGestureEvent.flags;
+ && mKeyGestureEvent.flags == that.mKeyGestureEvent.flags
+ && Objects.equals(mKeyGestureEvent.appLaunchCategory, that.mKeyGestureEvent.appLaunchCategory)
+ && Objects.equals(mKeyGestureEvent.appLaunchRole, that.mKeyGestureEvent.appLaunchRole)
+ && Objects.equals(mKeyGestureEvent.appLaunchPackageName, that.mKeyGestureEvent.appLaunchPackageName)
+ && Objects.equals(mKeyGestureEvent.appLaunchClassName, that.mKeyGestureEvent.appLaunchClassName);
}
@Override
@@ -352,13 +411,21 @@ public final class KeyGestureEvent {
_hash = 31 * _hash + mKeyGestureEvent.action;
_hash = 31 * _hash + mKeyGestureEvent.displayId;
_hash = 31 * _hash + mKeyGestureEvent.flags;
+ _hash = 31 * _hash + (mKeyGestureEvent.appLaunchCategory != null
+ ? mKeyGestureEvent.appLaunchCategory.hashCode() : 0);
+ _hash = 31 * _hash + (mKeyGestureEvent.appLaunchRole != null
+ ? mKeyGestureEvent.appLaunchRole.hashCode() : 0);
+ _hash = 31 * _hash + (mKeyGestureEvent.appLaunchPackageName != null
+ ? mKeyGestureEvent.appLaunchPackageName.hashCode() : 0);
+ _hash = 31 * _hash + (mKeyGestureEvent.appLaunchClassName != null
+ ? mKeyGestureEvent.appLaunchClassName.hashCode() : 0);
return _hash;
}
/**
* Convert KeyGestureEvent type to corresponding log event got KeyboardSystemsEvent
*/
- public static int keyGestureTypeToLogEvent(@KeyGestureType int value) {
+ private static int keyGestureTypeToLogEvent(@KeyGestureType int value) {
switch (value) {
case KEY_GESTURE_TYPE_HOME:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
@@ -460,14 +527,79 @@ public final class KeyGestureEvent {
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
- case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+ case KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
case KEY_GESTURE_TYPE_DESKTOP_MODE:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
default:
- return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+ return LOG_EVENT_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Find Log event type corresponding to app launch data.
+ * Returns {@code LOG_EVENT_UNSPECIFIED} if no matching event found
+ */
+ private static int getLogEventFromLaunchAppData(@Nullable AppLaunchData data) {
+ if (data == null) {
+ return LOG_EVENT_UNSPECIFIED;
+ }
+ if (data instanceof AppLaunchData.CategoryData) {
+ return getLogEventFromSelectorCategory(
+ ((AppLaunchData.CategoryData) data).getCategory());
+ } else if (data instanceof AppLaunchData.RoleData) {
+ return getLogEventFromRole(((AppLaunchData.RoleData) data).getRole());
+ } else if (data instanceof AppLaunchData.ComponentData) {
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+ } else {
+ throw new IllegalArgumentException("AppLaunchData type is invalid!");
+ }
+ }
+
+ private static int getLogEventFromSelectorCategory(@NonNull String category) {
+ switch (category) {
+ case Intent.CATEGORY_APP_BROWSER:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+ case Intent.CATEGORY_APP_EMAIL:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+ case Intent.CATEGORY_APP_CONTACTS:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+ case Intent.CATEGORY_APP_CALENDAR:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+ case Intent.CATEGORY_APP_CALCULATOR:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+ case Intent.CATEGORY_APP_MUSIC:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+ case Intent.CATEGORY_APP_MAPS:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+ case Intent.CATEGORY_APP_MESSAGING:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+ case Intent.CATEGORY_APP_GALLERY:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+ case Intent.CATEGORY_APP_FILES:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+ case Intent.CATEGORY_APP_WEATHER:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+ case Intent.CATEGORY_APP_FITNESS:
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+ default:
+ return LOG_EVENT_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Find Log event corresponding to the provide system role name.
+ * Returns {@code LOG_EVENT_UNSPECIFIED} if no matching event found.
+ */
+ private static int getLogEventFromRole(@NonNull String role) {
+ if (RoleManager.ROLE_BROWSER.equals(role)) {
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+ } else if (RoleManager.ROLE_SMS.equals(role)) {
+ return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+ } else {
+ return LOG_EVENT_UNSPECIFIED;
}
}
@@ -577,8 +709,8 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER";
case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS:
return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS";
- case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
- return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+ case KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
+ return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION";
case KEY_GESTURE_TYPE_DESKTOP_MODE:
return "KEY_GESTURE_TYPE_DESKTOP_MODE";
case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a1e7567faead..c6fd0ee3c80d 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1627,7 +1627,7 @@ public class SoundTrigger {
boolean allowMultipleTriggers = in.readBoolean();
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
- byte[] data = in.readBlob();
+ byte[] data = in.createByteArray();
int audioCapabilities = in.readInt();
return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
audioCapabilities);
@@ -1638,7 +1638,7 @@ public class SoundTrigger {
dest.writeBoolean(mCaptureRequested);
dest.writeBoolean(mAllowMultipleTriggers);
dest.writeTypedArray(mKeyphrases, flags);
- dest.writeBlob(mData);
+ dest.writeByteArray(mData);
dest.writeInt(mAudioCapabilities);
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a89483394611..13d7e3c2fbfd 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1274,6 +1274,12 @@ public class Build {
* Vanilla Ice Cream.
*/
public static final int VANILLA_ICE_CREAM = 35;
+
+ /**
+ * Baklava.
+ */
+ @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
+ public static final int BAKLAVA = CUR_DEVELOPMENT;
}
/** @hide */
@@ -1313,6 +1319,7 @@ public class Build {
VERSION_CODES_FULL.TIRAMISU,
VERSION_CODES_FULL.UPSIDE_DOWN_CAKE,
VERSION_CODES_FULL.VANILLA_ICE_CREAM,
+ VERSION_CODES_FULL.BAKLAVA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SdkIntFull {}
@@ -1514,6 +1521,11 @@ public class Build {
*/
public static final int VANILLA_ICE_CREAM =
VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER;
+
+ /**
+ * The upcoming, not yet finalized, version of Android.
+ */
+ public static final int BAKLAVA = VERSION_CODES.BAKLAVA * SDK_INT_MULTIPLIER;
}
/**
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 1b9235b21369..89b38d8048e5 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -23,7 +23,6 @@ import static com.android.window.flags.Flags.predictiveBackTimestampApi;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
-import android.util.TimeUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,7 +75,7 @@ public final class BackEvent {
}
/**
- * Creates a new {@link BackEvent} instance with the current uptime as frame time.
+ * Creates a new {@link BackEvent} instance with a frame time of 0.
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
@@ -88,7 +87,7 @@ public final class BackEvent {
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mFrameTimeMillis = System.nanoTime() / TimeUtils.NANOS_PER_MS;
+ mFrameTimeMillis = 0;
}
/**
diff --git a/core/java/com/android/internal/os/ApplicationSharedMemory.java b/core/java/com/android/internal/os/ApplicationSharedMemory.java
index 84f713edcc1a..e6ea29e483f1 100644
--- a/core/java/com/android/internal/os/ApplicationSharedMemory.java
+++ b/core/java/com/android/internal/os/ApplicationSharedMemory.java
@@ -21,6 +21,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
import libcore.io.IoUtils;
@@ -293,4 +294,34 @@ public class ApplicationSharedMemory implements AutoCloseable {
throw new IllegalStateException("Not mutable");
}
}
+
+ /**
+ * Return true if the memory has been mapped. This never throws.
+ */
+ public boolean isMapped() {
+ return mPtr != 0;
+ }
+
+ /**
+ * Return true if the memory is mapped and mutable. This never throws. Note that it returns
+ * false if the memory is not mapped.
+ */
+ public boolean isMutable() {
+ return isMapped() && mMutable;
+ }
+
+ /**
+ * Provide access to the nonce block needed by {@link PropertyInvalidatedCache}. This method
+ * returns 0 if the shared memory is not (yet) mapped.
+ */
+ public long getSystemNonceBlock() {
+ return isMapped() ? nativeGetSystemNonceBlock(mPtr) : 0;
+ }
+
+ /**
+ * Return a pointer to the system nonce cache in the shared memory region. The method is
+ * idempotent.
+ */
+ @FastNative
+ private static native long nativeGetSystemNonceBlock(long ptr);
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index d3b1f972a955..51a9cf67b06f 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -21,6 +21,9 @@ import android.annotation.Nullable;
import android.app.Notification.ProgressStyle;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.LayerDrawable;
@@ -52,7 +55,7 @@ import java.util.TreeSet;
* represent Notification ProgressStyle progress, such as for ridesharing and navigation.
*/
@RemoteViews.RemoteView
-public class NotificationProgressBar extends ProgressBar {
+public final class NotificationProgressBar extends ProgressBar {
private static final String TAG = "NotificationProgressBar";
private NotificationProgressModel mProgressModel;
@@ -61,7 +64,7 @@ public class NotificationProgressBar extends ProgressBar {
private List<Part> mProgressDrawableParts = null;
@Nullable
- private Drawable mProgressTrackerDrawable = null;
+ private Drawable mTracker = null;
public NotificationProgressBar(Context context) {
this(context, null);
@@ -78,28 +81,44 @@ public class NotificationProgressBar extends ProgressBar {
public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.NotificationProgressBar, defStyleAttr, defStyleRes);
+ saveAttributeDataForStyleable(context, R.styleable.NotificationProgressBar, attrs, a,
+ defStyleAttr,
+ defStyleRes);
+
+ // Supports setting the tracker in xml, but ProgressStyle notifications set/override it
+ // via {@code setProgressTrackerIcon}.
+ final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
+ setTracker(tracker);
}
/**
* Setter for the notification progress model.
*
* @see NotificationProgressModel#fromBundle
- * @see #setProgressModelAsync
*/
- @RemotableViewMethod(asyncImpl = "setProgressModelAsync")
+ @RemotableViewMethod
public void setProgressModel(@Nullable Bundle bundle) {
Preconditions.checkArgument(bundle != null,
"Bundle shouldn't be null");
mProgressModel = NotificationProgressModel.fromBundle(bundle);
+ final boolean isIndeterminate = mProgressModel.isIndeterminate();
+ setIndeterminate(isIndeterminate);
- if (mProgressModel.isIndeterminate()) {
+ if (isIndeterminate) {
final int indeterminateColor = mProgressModel.getIndeterminateColor();
setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor));
} else {
+ final int progress = mProgressModel.getProgress();
+ final int progressMax = mProgressModel.getProgressMax();
mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(),
mProgressModel.getPoints(),
- mProgressModel.getProgress(), mProgressModel.isStyledByProgress());
+ progress,
+ progressMax,
+ mProgressModel.isStyledByProgress());
try {
final NotificationProgressDrawable drawable = getNotificationProgressDrawable();
@@ -107,6 +126,9 @@ public class NotificationProgressBar extends ProgressBar {
} catch (IllegalStateException ex) {
Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex);
}
+
+ setMax(progressMax);
+ setProgress(progress);
}
}
@@ -137,6 +159,13 @@ public class NotificationProgressBar extends ProgressBar {
*/
@RemotableViewMethod(asyncImpl = "setProgressTrackerIconAsync")
public void setProgressTrackerIcon(@Nullable Icon icon) {
+ final Drawable progressTrackerDrawable;
+ if (icon != null) {
+ progressTrackerDrawable = icon.loadDrawable(getContext());
+ } else {
+ progressTrackerDrawable = null;
+ }
+ setTracker(progressTrackerDrawable);
}
/**
@@ -150,12 +179,245 @@ public class NotificationProgressBar extends ProgressBar {
progressTrackerDrawable = null;
}
return () -> {
- setProgressTrackerDrawable(progressTrackerDrawable);
+ setTracker(progressTrackerDrawable);
};
}
- private void setProgressTrackerDrawable(@Nullable Drawable drawable) {
- mProgressTrackerDrawable = drawable;
+ private void setTracker(@Nullable Drawable tracker) {
+ if (isIndeterminate() && tracker != null) {
+ return;
+ }
+
+ final boolean needUpdate = mTracker != null && tracker != mTracker;
+ if (needUpdate) {
+ mTracker.setCallback(null);
+ }
+
+ if (tracker != null) {
+ tracker.setCallback(this);
+ if (canResolveLayoutDirection()) {
+ tracker.setLayoutDirection(getLayoutDirection());
+ }
+
+ // If we're updating get the new states
+ if (needUpdate && (tracker.getIntrinsicWidth() != mTracker.getIntrinsicWidth()
+ || tracker.getIntrinsicHeight() != mTracker.getIntrinsicHeight())) {
+ requestLayout();
+ }
+ }
+
+ mTracker = tracker;
+ invalidate();
+
+ if (needUpdate) {
+ updateTrackerAndBarPos(getWidth(), getHeight());
+ if (tracker != null && tracker.isStateful()) {
+ // Note that if the states are different this won't work.
+ // For now, let's consider that an app bug.
+ tracker.setState(getDrawableState());
+ }
+ }
+ }
+
+ @Override
+ @RemotableViewMethod
+ public synchronized void setIndeterminate(boolean indeterminate) {
+ super.setIndeterminate(indeterminate);
+
+ if (isIndeterminate()) {
+ setTracker(null);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ return who == mTracker || super.verifyDrawable(who);
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+
+ if (mTracker != null) {
+ mTracker.jumpToCurrentState();
+ }
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ final Drawable tracker = mTracker;
+ if (tracker != null && tracker.isStateful()
+ && tracker.setState(getDrawableState())) {
+ invalidateDrawable(tracker);
+ }
+ }
+
+ @Override
+ public void drawableHotspotChanged(float x, float y) {
+ super.drawableHotspotChanged(x, y);
+
+ if (mTracker != null) {
+ mTracker.setHotspot(x, y);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ updateTrackerAndBarPos(w, h);
+ }
+
+ private void updateTrackerAndBarPos(int w, int h) {
+ final int paddedHeight = h - mPaddingTop - mPaddingBottom;
+ final Drawable bar = getCurrentDrawable();
+ final Drawable tracker = mTracker;
+
+ // The max height does not incorporate padding, whereas the height
+ // parameter does.
+ final int barHeight = Math.min(getMaxHeight(), paddedHeight);
+ final int trackerHeight = tracker == null ? 0 : tracker.getIntrinsicHeight();
+
+ // Apply offset to whichever item is taller.
+ final int barOffsetY;
+ final int trackerOffsetY;
+ if (trackerHeight > barHeight) {
+ final int offsetHeight = (paddedHeight - trackerHeight) / 2;
+ barOffsetY = offsetHeight + (trackerHeight - barHeight) / 2;
+ trackerOffsetY = offsetHeight;
+ } else {
+ final int offsetHeight = (paddedHeight - barHeight) / 2;
+ barOffsetY = offsetHeight;
+ trackerOffsetY = offsetHeight + (barHeight - trackerHeight) / 2;
+ }
+
+ if (bar != null) {
+ final int barWidth = w - mPaddingRight - mPaddingLeft;
+ bar.setBounds(0, barOffsetY, barWidth, barOffsetY + barHeight);
+ }
+
+ if (tracker != null) {
+ setTrackerPos(w, tracker, getScale(), trackerOffsetY);
+ }
+ }
+
+ private float getScale() {
+ int min = getMin();
+ int max = getMax();
+ int range = max - min;
+ return range > 0 ? (getProgress() - min) / (float) range : 0;
+ }
+
+ /**
+ * Updates the tracker drawable bounds.
+ *
+ * @param w Width of the view, including padding
+ * @param tracker Drawable used for the tracker
+ * @param scale Current progress between 0 and 1
+ * @param offsetY Vertical offset for centering. If set to
+ * {@link Integer#MIN_VALUE}, the current offset will be used.
+ */
+ private void setTrackerPos(int w, Drawable tracker, float scale, int offsetY) {
+ int available = w - mPaddingLeft - mPaddingRight;
+ final int trackerWidth = tracker.getIntrinsicWidth();
+ final int trackerHeight = tracker.getIntrinsicHeight();
+ available -= trackerWidth;
+
+ final int trackerPos = (int) (scale * available + 0.5f);
+
+ final int top, bottom;
+ if (offsetY == Integer.MIN_VALUE) {
+ final Rect oldBounds = tracker.getBounds();
+ top = oldBounds.top;
+ bottom = oldBounds.bottom;
+ } else {
+ top = offsetY;
+ bottom = offsetY + trackerHeight;
+ }
+
+ final int left = (isLayoutRtl() && getMirrorForRtl()) ? available - trackerPos : trackerPos;
+ final int right = left + trackerWidth;
+
+ final Drawable background = getBackground();
+ if (background != null) {
+ final int bkgOffsetX = mPaddingLeft;
+ final int bkgOffsetY = mPaddingTop;
+ background.setHotspotBounds(left + bkgOffsetX, top + bkgOffsetY,
+ right + bkgOffsetX, bottom + bkgOffsetY);
+ }
+
+ // Canvas will be translated, so 0,0 is where we start drawing
+ tracker.setBounds(left, top, right, bottom);
+ }
+
+ @Override
+ public void onResolveDrawables(int layoutDirection) {
+ super.onResolveDrawables(layoutDirection);
+
+ if (mTracker != null) {
+ mTracker.setLayoutDirection(layoutDirection);
+ }
+ }
+
+ @Override
+ protected synchronized void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ drawTracker(canvas);
+ }
+
+ /**
+ * Draw the tracker.
+ */
+ private void drawTracker(Canvas canvas) {
+ if (mTracker != null) {
+ final int saveCount = canvas.save();
+ // Translate the padding. For the x, we need to allow the tracker to
+ // draw in its extra space
+ canvas.translate(mPaddingLeft, mPaddingTop);
+ mTracker.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ @Override
+ protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Drawable d = getCurrentDrawable();
+
+ int trackerHeight = mTracker == null ? 0 : mTracker.getIntrinsicHeight();
+ int dw = 0;
+ int dh = 0;
+ if (d != null) {
+ dw = Math.max(getMinWidth(), Math.min(getMaxWidth(), d.getIntrinsicWidth()));
+ dh = Math.max(getMinHeight(), Math.min(getMaxHeight(), d.getIntrinsicHeight()));
+ dh = Math.max(trackerHeight, dh);
+ }
+ dw += mPaddingLeft + mPaddingRight;
+ dh += mPaddingTop + mPaddingBottom;
+
+ setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
+ resolveSizeAndState(dh, heightMeasureSpec, 0));
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return NotificationProgressBar.class.getName();
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ final Drawable tracker = mTracker;
+ if (tracker != null) {
+ setTrackerPos(getWidth(), tracker, getScale(), Integer.MIN_VALUE);
+
+ // Since we draw translated, the drawable's bounds that it signals
+ // for invalidation won't be the actual bounds we want invalidated,
+ // so just invalidate this whole view.
+ invalidate();
+ }
}
/**
@@ -167,12 +429,18 @@ public class NotificationProgressBar extends ProgressBar {
List<ProgressStyle.Segment> segments,
List<ProgressStyle.Point> points,
int progress,
+ int progressMax,
boolean isStyledByProgress
) {
if (segments.isEmpty()) {
throw new IllegalArgumentException("List of segments shouldn't be empty");
}
+ final int totalLength = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum();
+ if (progressMax != totalLength) {
+ throw new IllegalArgumentException("Invalid progressMax : " + progressMax);
+ }
+
for (ProgressStyle.Segment segment : segments) {
final int length = segment.getLength();
if (length <= 0) {
@@ -180,8 +448,6 @@ public class NotificationProgressBar extends ProgressBar {
}
}
- final int progressMax = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum();
-
if (progress < 0 || progress > progressMax) {
throw new IllegalArgumentException("Invalid progress : " + progress);
}
@@ -208,7 +474,6 @@ public class NotificationProgressBar extends ProgressBar {
isStyledByProgress);
}
-
// Any segment with a point on it gets split by the point.
// If isStyledByProgress is true, also split the segment with the progress value in its range.
private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPointsAndProgress(
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e51ea99ac4f5..e8cb37e8f19b 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -96,6 +96,10 @@ public final class NotificationProgressModel {
return mProgress;
}
+ public int getProgressMax() {
+ return mSegments.stream().mapToInt(Notification.ProgressStyle.Segment::getLength).sum();
+ }
+
public boolean isStyledByProgress() {
return mIsStyledByProgress;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 816ace2310a5..eb07f7c125d0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -249,6 +249,7 @@ cc_library_shared_for_libandroid_runtime {
"android_backup_BackupDataOutput.cpp",
"android_backup_FileBackupHelperBase.cpp",
"android_backup_BackupHelperDispatcher.cpp",
+ "android_app_PropertyInvalidatedCache.cpp",
"android_app_backup_FullBackup.cpp",
"android_content_res_ApkAssets.cpp",
"android_content_res_ObbScanner.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 76f66cd4ebc9..821861efd59b 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,6 +177,7 @@ extern int register_android_app_backup_FullBackup(JNIEnv *env);
extern int register_android_app_Activity(JNIEnv *env);
extern int register_android_app_ActivityThread(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
+extern int register_android_app_PropertyInvalidatedCache(JNIEnv* env);
extern int register_android_media_RemoteDisplay(JNIEnv *env);
extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
extern int register_android_view_InputChannel(JNIEnv* env);
@@ -1659,6 +1660,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_app_Activity),
REG_JNI(register_android_app_ActivityThread),
REG_JNI(register_android_app_NativeActivity),
+ REG_JNI(register_android_app_PropertyInvalidatedCache),
REG_JNI(register_android_util_jar_StrictJarFile),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputEventReceiver),
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp
new file mode 100644
index 000000000000..ead66660a0a4
--- /dev/null
+++ b/core/jni/android_app_PropertyInvalidatedCache.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#define LOG_TAG "CacheNonce"
+
+#include <string.h>
+#include <memory.h>
+
+#include <atomic>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_primitive_array.h>
+#include <android-base/logging.h>
+
+#include "core_jni_helpers.h"
+#include "android_app_PropertyInvalidatedCache.h"
+
+namespace {
+
+using namespace android::app::PropertyInvalidatedCache;
+
+// Convert a jlong to a nonce block. This is a convenience function that should be inlined by
+// the compiler.
+inline SystemCacheNonce* sysCache(jlong ptr) {
+ return reinterpret_cast<SystemCacheNonce*>(ptr);
+}
+
+// Return the number of nonces in the nonce block.
+jint getMaxNonce(JNIEnv*, jclass, jlong ptr) {
+ return sysCache(ptr)->getMaxNonce();
+}
+
+// Return the number of string bytes in the nonce block.
+jint getMaxByte(JNIEnv*, jclass, jlong ptr) {
+ return sysCache(ptr)->getMaxByte();
+}
+
+// Set the byte block. The first int is the hash to set and the second is the array to copy.
+// This should be synchronized in the Java layer.
+void setByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) {
+ ScopedByteArrayRO value(env, val);
+ if (value.get() == nullptr) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "null byte block");
+ return;
+ }
+ sysCache(ptr)->setByteBlock(hash, value.get(), value.size());
+}
+
+// Fetch the byte block. If the incoming hash is the same as the local hash, the Java layer is
+// presumed to have an up-to-date copy of the byte block; do not copy byte array. The local
+// hash is returned.
+jint getByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) {
+ if (sysCache(ptr)->getHash() == hash) {
+ return hash;
+ }
+ ScopedByteArrayRW value(env, val);
+ return sysCache(ptr)->getByteBlock(value.get(), value.size());
+}
+
+// Fetch the byte block hash.
+//
+// This is a CriticalNative method and therefore does not get the JNIEnv or jclass parameters.
+jint getByteBlockHash(jlong ptr) {
+ return sysCache(ptr)->getHash();
+}
+
+// Get a nonce value. So that this method can be CriticalNative, it returns 0 if the value is
+// out of range, rather than throwing an exception. This is a CriticalNative method and
+// therefore does not get the JNIEnv or jclass parameters.
+//
+// This method is @CriticalNative and does not take a JNIEnv* or jclass argument.
+jlong getNonce(jlong ptr, jint index) {
+ return sysCache(ptr)->getNonce(index);
+}
+
+// Set a nonce value. So that this method can be CriticalNative, it returns a boolean: false if
+// the index is out of range and true otherwise. Callers may test the returned boolean and
+// generate an exception.
+//
+// This method is @CriticalNative and does not take a JNIEnv* or jclass argument.
+jboolean setNonce(jlong ptr, jint index, jlong value) {
+ return sysCache(ptr)->setNonce(index, value);
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nativeGetMaxNonce", "(J)I", (void*) getMaxNonce },
+ {"nativeGetMaxByte", "(J)I", (void*) getMaxByte },
+ {"nativeSetByteBlock", "(JI[B)V", (void*) setByteBlock },
+ {"nativeGetByteBlock", "(JI[B)I", (void*) getByteBlock },
+ {"nativeGetByteBlockHash", "(J)I", (void*) getByteBlockHash },
+ {"nativeGetNonce", "(JI)J", (void*) getNonce },
+ {"nativeSetNonce", "(JIJ)Z", (void*) setNonce },
+};
+
+static const char* kClassName = "android/app/PropertyInvalidatedCache";
+
+} // anonymous namespace
+
+namespace android {
+
+int register_android_app_PropertyInvalidatedCache(JNIEnv* env) {
+ RegisterMethodsOrDie(env, kClassName, gMethods, NELEM(gMethods));
+ return JNI_OK;
+}
+
+} // namespace android
diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h
new file mode 100644
index 000000000000..eefa8fa88624
--- /dev/null
+++ b/core/jni/android_app_PropertyInvalidatedCache.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <string.h>
+#include <memory.h>
+
+#include <atomic>
+
+namespace android {
+namespace app {
+namespace PropertyInvalidatedCache {
+
+/**
+ * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The
+ * byte array has an associated hash. This class provides methods to read and write the fields
+ * of the block but it does not interpret the fields.
+ *
+ * On initialization, all fields are set to zero.
+ *
+ * In general, methods do not report errors. This allows the methods to be used in
+ * CriticalNative JNI APIs.
+ *
+ * The template is parameterized by the number of nonces it supports and the number of bytes in
+ * the string block.
+ */
+template<int maxNonce, size_t maxByte> class CacheNonce {
+
+ // The value of an unset field.
+ static const int UNSET = 0;
+
+ // A convenient typedef. The jbyteArray element type is jbyte, which the compiler treats as
+ // signed char.
+ typedef signed char block_t;
+
+ // The array of nonces
+ volatile std::atomic<int64_t> mNonce[maxNonce];
+
+ // The byte array. This is not atomic but it is guarded by the mByteHash.
+ volatile block_t mByteBlock[maxByte];
+
+ // The hash that validates the byte block
+ volatile std::atomic<int32_t> mByteHash;
+
+ // Pad the class to a multiple of 8 bytes.
+ int32_t _pad;
+
+ public:
+
+ // The expected size of this instance. This is a compile-time constant and can be used in a
+ // static assertion.
+ static const int expectedSize =
+ maxNonce * sizeof(std::atomic<int64_t>)
+ + sizeof(std::atomic<int32_t>)
+ + maxByte * sizeof(block_t)
+ + sizeof(int32_t);
+
+ // These provide run-time access to the sizing parameters.
+ int getMaxNonce() const {
+ return maxNonce;
+ }
+
+ size_t getMaxByte() const {
+ return maxByte;
+ }
+
+ // Construct and initialize the memory.
+ CacheNonce() {
+ for (int i = 0; i < maxNonce; i++) {
+ mNonce[i] = UNSET;
+ }
+ mByteHash = UNSET;
+ memset((void*) mByteBlock, UNSET, sizeof(mByteBlock));
+ }
+
+ // Fetch a nonce, returning UNSET if the index is out of range. This method specifically
+ // does not throw or generate an error if the index is out of range; this allows the method
+ // to be called in a CriticalNative JNI API.
+ int64_t getNonce(int index) const {
+ if (index < 0 || index >= maxNonce) {
+ return UNSET;
+ } else {
+ return mNonce[index];
+ }
+ }
+
+ // Set a nonce and return true. Return false if the index is out of range. This method
+ // specifically does not throw or generate an error if the index is out of range; this
+ // allows the method to be called in a CriticalNative JNI API.
+ bool setNonce(int index, int64_t value) {
+ if (index < 0 || index >= maxNonce) {
+ return false;
+ } else {
+ mNonce[index] = value;
+ return true;
+ }
+ }
+
+ // Fetch just the byte-block hash
+ int32_t getHash() const {
+ return mByteHash;
+ }
+
+ // Copy the byte block to the target and return the current hash.
+ int32_t getByteBlock(block_t* block, size_t len) const {
+ memcpy(block, (void*) mByteBlock, std::min(maxByte, len));
+ return mByteHash;
+ }
+
+ // Set the byte block and the hash.
+ void setByteBlock(int hash, const block_t* block, size_t len) {
+ memcpy((void*) mByteBlock, block, len = std::min(maxByte, len));
+ mByteHash = hash;
+ }
+};
+
+/**
+ * Sizing parameters for the system_server PropertyInvalidatedCache support. A client can
+ * retrieve the values through the accessors in CacheNonce instances.
+ */
+static const int MAX_NONCE = 64;
+static const int BYTE_BLOCK_SIZE = 8192;
+
+// The CacheNonce for system server holds 64 nonces with a string block of 8192 bytes.
+typedef CacheNonce<MAX_NONCE, BYTE_BLOCK_SIZE> SystemCacheNonce;
+
+// The goal of this assertion is to ensure that the data structure is the same size across 32-bit
+// and 64-bit systems.
+static_assert(sizeof(SystemCacheNonce) == SystemCacheNonce::expectedSize,
+ "Unexpected SystemCacheNonce size");
+
+} // namespace PropertyInvalidatedCache
+} // namespace app
+} // namespace android
diff --git a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
index 453e53974e0d..cc1687cd9ffb 100644
--- a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
+++ b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
@@ -29,8 +29,12 @@
#include "core_jni_helpers.h"
+#include "android_app_PropertyInvalidatedCache.h"
+
namespace {
+using namespace android::app::PropertyInvalidatedCache;
+
// Atomics should be safe to use across processes if they are lock free.
static_assert(std::atomic<int64_t>::is_always_lock_free == true,
"atomic<int64_t> is not always lock free");
@@ -64,12 +68,15 @@ public:
void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(int64_t offset) {
latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = offset;
}
+
+ // The nonce storage for pic. The sizing is suitable for the system server module.
+ SystemCacheNonce systemPic;
};
// Update the expected value when modifying the members of SharedMemory.
// The goal of this assertion is to ensure that the data structure is the same size across 32-bit
// and 64-bit systems.
-static_assert(sizeof(SharedMemory) == 8, "Unexpected SharedMemory size");
+static_assert(sizeof(SharedMemory) == 8 + sizeof(SystemCacheNonce), "Unexpected SharedMemory size");
static jint nativeCreate(JNIEnv* env, jclass) {
// Create anonymous shared memory region
@@ -133,6 +140,12 @@ static jlong nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMilli
return sharedMemory->getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
}
+// This is a FastNative method. It takes the usual JNIEnv* and jclass* arguments.
+static jlong nativeGetSystemNonceBlock(JNIEnv*, jclass*, jlong ptr) {
+ SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
+ return reinterpret_cast<jlong>(&sharedMemory->systemPic);
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeCreate", "()I", (void*)nativeCreate},
{"nativeMap", "(IZ)J", (void*)nativeMap},
@@ -143,16 +156,17 @@ static const JNINativeMethod gMethods[] = {
(void*)nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
{"nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(J)J",
(void*)nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
+ {"nativeGetSystemNonceBlock", "(J)J", (void*) nativeGetSystemNonceBlock},
};
-} // anonymous namespace
-
-namespace android {
-
static const char kApplicationSharedMemoryClassName[] =
"com/android/internal/os/ApplicationSharedMemory";
static jclass gApplicationSharedMemoryClass;
+} // anonymous namespace
+
+namespace android {
+
int register_com_android_internal_os_ApplicationSharedMemory(JNIEnv* env) {
gApplicationSharedMemoryClass =
MakeGlobalRefOrDie(env, FindClassOrDie(env, kApplicationSharedMemoryClassName));
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 02f9f3c5f0db..990cbb47540a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5731,6 +5731,12 @@
</attr>
</declare-styleable>
+ <!-- @hide internal use only -->
+ <declare-styleable name="NotificationProgressBar">
+ <!-- Draws the tracker on a NotificationProgressBar. -->
+ <attr name="tracker" format="reference" />
+ </declare-styleable>
+
<declare-styleable name="StackView">
<!-- Color of the res-out outline. -->
<attr name="resOutColor" format="color" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java
new file mode 100644
index 000000000000..7afdde244073
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2024 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 android.hardware.radio;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@EnableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+public final class RadioAlertUnitTest {
+
+ private static final int TEST_FLAGS = 0;
+ private static final int CREATOR_ARRAY_SIZE = 3;
+ private static final String TEST_GEOCODE_VALUE_NAME = "SAME";
+ private static final String TEST_GEOCODE_VALUE_1 = "006109";
+ private static final String TEST_GEOCODE_VALUE_2 = "006009";
+ private static final double TEST_POLYGON_LATITUDE_START = -38.47;
+ private static final double TEST_POLYGON_LONGITUDE_START = -120.14;
+ private static final RadioAlert.Coordinate TEST_POLYGON_COORDINATE_START =
+ new RadioAlert.Coordinate(TEST_POLYGON_LATITUDE_START, TEST_POLYGON_LONGITUDE_START);
+ private static final List<RadioAlert.Coordinate> TEST_COORDINATES = List.of(
+ TEST_POLYGON_COORDINATE_START, new RadioAlert.Coordinate(38.34, -119.95),
+ new RadioAlert.Coordinate(38.52, -119.74), new RadioAlert.Coordinate(38.62, -119.89),
+ TEST_POLYGON_COORDINATE_START);
+ private static final RadioAlert.Polygon TEST_POLYGON = new RadioAlert.Polygon(TEST_COORDINATES);
+ private static final RadioAlert.Geocode TEST_GEOCODE_1 = new RadioAlert.Geocode(
+ TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_1);
+ private static final RadioAlert.Geocode TEST_GEOCODE_2 = new RadioAlert.Geocode(
+ TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_2);
+ private static final RadioAlert.AlertArea TEST_AREA_1 = new RadioAlert.AlertArea(
+ List.of(TEST_POLYGON), List.of(TEST_GEOCODE_1));
+ private static final RadioAlert.AlertArea TEST_AREA_2 = new RadioAlert.AlertArea(
+ new ArrayList<>(), List.of(TEST_GEOCODE_1, TEST_GEOCODE_2));
+
+ @Rule
+ public final Expect mExpect = Expect.create();
+
+ @Test
+ public void constructor_withNullValueName_forGeocode_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new RadioAlert.Geocode(/* valueName= */ null, TEST_GEOCODE_VALUE_1));
+
+ mExpect.withMessage("Exception for geocode constructor with null value name")
+ .that(thrown).hasMessageThat()
+ .contains("Geocode value name can not be null");
+ }
+
+ @Test
+ public void constructor_withNullValue_forGeocode_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new RadioAlert.Geocode(TEST_GEOCODE_VALUE_NAME, /* value= */ null));
+
+ mExpect.withMessage("Exception for geocode constructor with null value")
+ .that(thrown).hasMessageThat()
+ .contains("Geocode value can not be null");
+ }
+
+ @Test
+ public void getValueName_forGeocode() {
+ mExpect.withMessage("Value name of geocode").that(TEST_GEOCODE_1.getValueName())
+ .isEqualTo(TEST_GEOCODE_VALUE_NAME);
+ }
+
+ @Test
+ public void getValue_forGeocode() {
+ mExpect.withMessage("Value of geocode").that(TEST_GEOCODE_1.getValue())
+ .isEqualTo(TEST_GEOCODE_VALUE_1);
+ }
+
+ @Test
+ public void describeContents_forGeocode() {
+ mExpect.withMessage("Contents of geocode")
+ .that(TEST_GEOCODE_1.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forGeocode() {
+ Parcel parcel = Parcel.obtain();
+
+ TEST_GEOCODE_1.writeToParcel(parcel, TEST_FLAGS);
+
+ parcel.setDataPosition(0);
+ RadioAlert.Geocode geocodeFromParcel = RadioAlert.Geocode.CREATOR.createFromParcel(parcel);
+ mExpect.withMessage("Geocode from parcel").that(geocodeFromParcel)
+ .isEqualTo(TEST_GEOCODE_1);
+ }
+
+ @Test
+ public void newArray_forGeocodeCreator() {
+ RadioAlert.Geocode[] geocodes = RadioAlert.Geocode.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ mExpect.withMessage("Geocodes").that(geocodes).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void hashCode_withSameGeocodes() {
+ RadioAlert.Geocode geocodeCompared = new RadioAlert.Geocode(TEST_GEOCODE_VALUE_NAME,
+ TEST_GEOCODE_VALUE_1);
+
+ mExpect.withMessage("Hash code of the same gecode")
+ .that(geocodeCompared.hashCode()).isEqualTo(TEST_GEOCODE_1.hashCode());
+ }
+
+ @Test
+ public void equals_withDifferentGeocodes() {
+ mExpect.withMessage("Different geocode").that(TEST_GEOCODE_1)
+ .isNotEqualTo(TEST_GEOCODE_2);
+ }
+
+ @Test
+ @SuppressWarnings("TruthIncompatibleType")
+ public void equals_withDifferentTypeObject_forGeocode() {
+ mExpect.withMessage("Non-geocode object").that(TEST_GEOCODE_1)
+ .isNotEqualTo(TEST_POLYGON_COORDINATE_START);
+ }
+
+ @Test
+ public void constructor_withInvalidLatitude_forCoordinate_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new RadioAlert.Coordinate(/* latitude= */ -92.0, TEST_POLYGON_LONGITUDE_START));
+
+ mExpect.withMessage("Exception for coordinate constructor with invalid latitude")
+ .that(thrown).hasMessageThat().contains("Latitude");
+ }
+
+ @Test
+ public void constructor_withInvalidLongitude_forCoordinate_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new RadioAlert.Coordinate(TEST_POLYGON_LATITUDE_START, /* longitude= */ 200.0));
+
+ mExpect.withMessage("Exception for coordinate constructor with invalid longitude")
+ .that(thrown).hasMessageThat().contains("Longitude");
+ }
+
+ @Test
+ public void getLatitude_forCoordinate() {
+ mExpect.withMessage("Latitude of coordinate")
+ .that(TEST_POLYGON_COORDINATE_START.getLatitude())
+ .isEqualTo(TEST_POLYGON_LATITUDE_START);
+ }
+
+ @Test
+ public void getLongitude_forCoordinate() {
+ mExpect.withMessage("Longitude of coordinate")
+ .that(TEST_POLYGON_COORDINATE_START.getLongitude())
+ .isEqualTo(TEST_POLYGON_LONGITUDE_START);
+ }
+
+ @Test
+ public void describeContents_forCoordinate() {
+ mExpect.withMessage("Contents of coordinate")
+ .that(TEST_POLYGON_COORDINATE_START.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forCoordinate() {
+ Parcel parcel = Parcel.obtain();
+
+ TEST_POLYGON_COORDINATE_START.writeToParcel(parcel, TEST_FLAGS);
+
+ parcel.setDataPosition(0);
+ RadioAlert.Coordinate coordinateFromParcel = RadioAlert.Coordinate.CREATOR
+ .createFromParcel(parcel);
+ mExpect.withMessage("Coordinate from parcel").that(coordinateFromParcel)
+ .isEqualTo(TEST_POLYGON_COORDINATE_START);
+ }
+
+ @Test
+ public void newArray_forCoordinateCreator() {
+ RadioAlert.Coordinate[] coordinates = RadioAlert.Coordinate.CREATOR
+ .newArray(CREATOR_ARRAY_SIZE);
+
+ mExpect.withMessage("Coordinates").that(coordinates).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void hashCode_withSameCoordinates() {
+ RadioAlert.Coordinate coordinateCompared = new RadioAlert.Coordinate(
+ TEST_POLYGON_LATITUDE_START, TEST_POLYGON_LONGITUDE_START);
+
+ mExpect.withMessage("Hash code of the same coordinate")
+ .that(coordinateCompared.hashCode())
+ .isEqualTo(TEST_POLYGON_COORDINATE_START.hashCode());
+ }
+
+ @Test
+ public void equals_withDifferentCoordinates() {
+ mExpect.withMessage("Different coordinate").that(TEST_POLYGON_COORDINATE_START)
+ .isNotEqualTo(TEST_COORDINATES.get(1));
+ }
+
+ @Test
+ @SuppressWarnings("TruthIncompatibleType")
+ public void equals_withDifferentTypeObject_forCoordinate() {
+ mExpect.withMessage("Non-coordinate object").that(TEST_POLYGON_COORDINATE_START)
+ .isNotEqualTo(TEST_GEOCODE_1);
+ }
+
+ @Test
+ public void constructor_withNullCoordinates_forPolygon_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new RadioAlert.Polygon(/* coordinates= */ null));
+
+ mExpect.withMessage("Exception for polygon constructor with null coordinates")
+ .that(thrown).hasMessageThat().contains("Coordinates can not be null");
+ }
+
+ @Test
+ public void constructor_withLessThanFourCoordinates_forPolygon_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new RadioAlert.Polygon(List.of(TEST_POLYGON_COORDINATE_START,
+ TEST_POLYGON_COORDINATE_START)));
+
+ mExpect.withMessage("Exception for polygon constructor with less than four coordinates")
+ .that(thrown).hasMessageThat().contains("Number of coordinates must be at least 4");
+ }
+
+ @Test
+ public void constructor_withDifferentFirstAndLastCoordinates_forPolygon_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new RadioAlert.Polygon(List.of(TEST_POLYGON_COORDINATE_START,
+ new RadioAlert.Coordinate(38.34, -119.95),
+ new RadioAlert.Coordinate(38.52, -119.74),
+ new RadioAlert.Coordinate(38.62, -119.89))));
+
+ mExpect.withMessage(
+ "Exception for polygon constructor with different first and last coordinates")
+ .that(thrown).hasMessageThat().contains(
+ "last and first coordinates must be the same");
+ }
+
+ @Test
+ public void getCoordinates_forPolygon() {
+ mExpect.withMessage("Coordinates in polygon").that(TEST_POLYGON.getCoordinates())
+ .containsExactlyElementsIn(TEST_COORDINATES);
+ }
+
+ @Test
+ public void describeContents_forPolygon() {
+ mExpect.withMessage("Contents of polygon")
+ .that(TEST_POLYGON.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forPolygon() {
+ Parcel parcel = Parcel.obtain();
+
+ TEST_POLYGON.writeToParcel(parcel, TEST_FLAGS);
+
+ parcel.setDataPosition(0);
+ RadioAlert.Polygon polygonFromParcel = RadioAlert.Polygon.CREATOR.createFromParcel(parcel);
+ mExpect.withMessage("Polygon from parcel").that(polygonFromParcel)
+ .isEqualTo(TEST_POLYGON);
+ }
+
+ @Test
+ public void newArray_forPolygonCreator() {
+ RadioAlert.Polygon[] polygons = RadioAlert.Polygon.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ mExpect.withMessage("Polygons").that(polygons).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void hashCode_withSamePolygons() {
+ RadioAlert.Polygon polygonCompared = new RadioAlert.Polygon(TEST_COORDINATES);
+
+ mExpect.withMessage("Hash code of the same polygon")
+ .that(polygonCompared.hashCode()).isEqualTo(TEST_POLYGON.hashCode());
+ }
+
+ @Test
+ @SuppressWarnings("TruthIncompatibleType")
+ public void equals_withDifferentTypeObject_forPolygon() {
+ mExpect.withMessage("Non-polygon object").that(TEST_POLYGON)
+ .isNotEqualTo(TEST_GEOCODE_1);
+ }
+
+ @Test
+ public void constructor_withNullPolygons_forAlertArea_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new RadioAlert.AlertArea(/* polygons= */ null, List.of(TEST_GEOCODE_1)));
+
+ mExpect.withMessage("Exception for alert area constructor with null polygon list")
+ .that(thrown).hasMessageThat().contains("Polygons can not be null");
+ }
+
+ @Test
+ public void constructor_withNullGeocodes_forAlertArea_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new RadioAlert.AlertArea(List.of(TEST_POLYGON), /* geocodes= */ null));
+
+ mExpect.withMessage("Exception for alert area constructor with null geocode list")
+ .that(thrown).hasMessageThat().contains("Geocodes can not be null");
+ }
+
+ @Test
+ public void getPolygons_forAlertArea() {
+ mExpect.withMessage("Polygons in alert area").that(TEST_AREA_1.getPolygons())
+ .containsExactly(TEST_POLYGON);
+ }
+
+ @Test
+ public void getGeocodes_forAlertArea() {
+ mExpect.withMessage("Polygons in alert area").that(TEST_AREA_2.getGeocodes())
+ .containsExactly(TEST_GEOCODE_1, TEST_GEOCODE_2);
+ }
+
+ @Test
+ public void describeContents_forAlertArea() {
+ mExpect.withMessage("Contents of alert area")
+ .that(TEST_AREA_1.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forAlertArea() {
+ Parcel parcel = Parcel.obtain();
+
+ TEST_AREA_1.writeToParcel(parcel, TEST_FLAGS);
+
+ parcel.setDataPosition(0);
+ RadioAlert.AlertArea areaFromParcel = RadioAlert.AlertArea.CREATOR.createFromParcel(parcel);
+ mExpect.withMessage("Alert area from parcel").that(areaFromParcel)
+ .isEqualTo(TEST_AREA_1);
+ }
+
+ @Test
+ public void newArray_forAlertAreaCreator() {
+ RadioAlert.AlertArea[] alertAreas = RadioAlert.AlertArea.CREATOR
+ .newArray(CREATOR_ARRAY_SIZE);
+
+ mExpect.withMessage("Alert areas").that(alertAreas).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void hashCode_withSameAlertAreas() {
+ RadioAlert.AlertArea alertAreaCompared = new RadioAlert.AlertArea(List.of(TEST_POLYGON),
+ List.of(TEST_GEOCODE_1));
+
+ mExpect.withMessage("Hash code of the same alert area")
+ .that(alertAreaCompared.hashCode()).isEqualTo(TEST_AREA_1.hashCode());
+ }
+
+ @Test
+ public void equals_withDifferentAlertAreas() {
+ mExpect.withMessage("Different alert area").that(TEST_AREA_1).isNotEqualTo(TEST_AREA_2);
+ }
+
+ @Test
+ @SuppressWarnings("TruthIncompatibleType")
+ public void equals_withDifferentTypeObject_forAlertArea() {
+ mExpect.withMessage("Non-alert-area object").that(TEST_AREA_1)
+ .isNotEqualTo(TEST_GEOCODE_1);
+ }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 56e18e6c443f..aee1c3b2f28c 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -146,6 +146,10 @@ android_test {
":BinderProxyCountingTestService",
":AppThatUsesAppOps",
":AppThatCallsBinderMethods",
+ ":HelloWorldSdk1",
+ ":HelloWorldUsingSdk1AndSdk1",
+ ":HelloWorldUsingSdk1And2",
+ ":HelloWorldUsingSdkMalformedNegativeVersion",
],
}
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 05ab783c01bb..3bc81724bc0a 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -29,6 +29,18 @@
<option name="test-file-name" value="AppThatCallsBinderMethods.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true"/>
+ <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+ value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdk1And2.apk"/>
+ <option name="push-file" key="HelloWorldUsingSdk1AndSdk1.apk"
+ value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdk1AndSdk1.apk"/>
+ <option name="push-file" key="HelloWorldUsingSdkMalformedNegativeVersion.apk"
+ value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdkMalformedNegativeVersion.apk"/>
+ <option name="push-file" key="HelloWorldSdk1.apk"
+ value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/>
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
<option name="run-command" value="settings put global device_config_sync_disabled 0" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 23a09857032c..a2ff4ca0f4a3 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.Notification.Action.EXTRA_DATA_ONLY_INPUTS;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_READ;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_REPLY;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_REMOTE_INPUT;
@@ -97,6 +98,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Pair;
+import android.view.View;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -106,10 +108,10 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.util.ContrastColorUtil;
-import junit.framework.Assert;
-
import libcore.junit.util.compat.CoreCompatChangeRule;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -545,6 +547,22 @@ public class NotificationTest {
}
@Test
+ public void largeIconMultipleReferences_ignoreBadData() {
+ Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build();
+ assertSame(n.getLargeIcon(), originalIcon);
+ n.extras.putParcelable(EXTRA_LARGE_ICON, new NotificationChannelGroup("hi", "hi"));
+
+ Notification q = writeAndReadParcelable(n);
+ assertNotSame(q.getLargeIcon(), n.getLargeIcon());
+
+ assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap()));
+ assertSame(q.getLargeIcon(), q.extras.getParcelable(EXTRA_LARGE_ICON));
+ }
+
+ @Test
public void largeIconReferenceInExtrasOnly_keptAfterParcelling() {
Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
@@ -2444,6 +2462,69 @@ public class NotificationTest {
assertThat(progressStyle1.isStyledByProgress()).isTrue();
}
+
+ @Test
+ public void getDataOnlyRemoteInputs_invalidData() {
+ Notification.Action action = new Notification.Action.Builder(0, "title", null)
+ .addRemoteInput(new RemoteInput.Builder("result")
+ .setAllowFreeFormInput(false)
+ .setAllowDataType("mimeType", true)
+ .build()).build();
+ action.getExtras().putParcelable(EXTRA_DATA_ONLY_INPUTS,
+ new NotificationChannelGroup("hi", "hi"));
+ assertThat(action.getDataOnlyRemoteInputs()).isNull();
+ }
+
+ @Test
+ public void actionAddExtras_invalidData() {
+ Bundle extras = new Bundle();
+ extras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS,
+ new NotificationChannelGroup[]{new NotificationChannelGroup("hi", "hi")});
+ Notification.Action action = new Notification.Action.Builder(0, "title", null)
+ .addRemoteInput(new RemoteInput.Builder("result")
+ .setAllowFreeFormInput(false)
+ .setAllowDataType("mimeType", true)
+ .addExtras(extras)
+ .build()).build();
+ assertThat(action.getDataOnlyRemoteInputs()[0].getClass()).isEqualTo(RemoteInput.class);
+ }
+
+ @Test
+ public void makeBigTemplate_invalidData() {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(mContext, NotificationTest.class),
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ RemoteInput remoteInput =
+ new RemoteInput.Builder("key_text_reply")
+ .setLabel("Reply")
+ .setChoices(new String[]{"Choice 1", "Choice 2"})
+ .addExtras(new Bundle())
+ .build();
+ Notification.Action replyAction = new Notification.Action.Builder(
+ R.drawable.stat_notify_chat, "Reply", pendingIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ new NotificationChannelGroup[]{});
+
+ Notification.Builder nb = new Notification.Builder(mContext, "channel")
+ .setContentTitle("title")
+ .setVisibility(android.app.Notification.VISIBILITY_PUBLIC)
+ .setStyle(new Notification.InboxStyle())
+ .setExtras(bundle)
+ .setSmallIcon(R.drawable.stat_notify_chat)
+ .addAction(replyAction);
+
+ RemoteViews views = nb.createBigContentView();
+ View view = views.apply(mContext, null);
+ assertThat(view.findViewById(R.id.notification_material_reply_container).getVisibility())
+ .isNotEqualTo(View.VISIBLE);
+ assertThat(view.findViewById(R.id.inbox_text0).getVisibility())
+ .isNotEqualTo(View.VISIBLE);
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index dcea5b299829..65153f55295a 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,13 +16,23 @@
package android.app;
+import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
+import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.internal.os.ApplicationSharedMemory;
+
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
@@ -47,6 +57,9 @@ public class PropertyInvalidatedCacheTests {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
// Configuration for creating caches
private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
private static final String API = "testApi";
@@ -423,4 +436,54 @@ public class PropertyInvalidatedCacheTests {
// Re-enable test mode (so that the cleanup for the test does not throw).
PropertyInvalidatedCache.setTestMode(true);
}
+
+ // Verify the behavior of shared memory nonce storage. This does not directly test the cache
+ // storing nonces in shared memory.
+ @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
+ @Test
+ public void testSharedMemoryStorage() {
+ // Fetch a shared memory instance for testing.
+ ApplicationSharedMemory shmem = ApplicationSharedMemory.create();
+
+ // Create a server-side store and a client-side store. The server's store is mutable and
+ // the client's store is not mutable.
+ PropertyInvalidatedCache.NonceStore server =
+ new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
+ PropertyInvalidatedCache.NonceStore client =
+ new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
+
+ final String name1 = "name1";
+ assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);
+ assertEquals(client.getHandleForName(name1), INVALID_NONCE_INDEX);
+ final int index1 = server.storeName(name1);
+ assertNotEquals(index1, INVALID_NONCE_INDEX);
+ assertEquals(server.getHandleForName(name1), index1);
+ assertEquals(client.getHandleForName(name1), index1);
+ assertEquals(server.storeName(name1), index1);
+
+ assertEquals(server.getNonce(index1), NONCE_UNSET);
+ assertEquals(client.getNonce(index1), NONCE_UNSET);
+ final int value1 = 4;
+ server.setNonce(index1, value1);
+ assertEquals(server.getNonce(index1), value1);
+ assertEquals(client.getNonce(index1), value1);
+ final int value2 = 8;
+ server.setNonce(index1, value2);
+ assertEquals(server.getNonce(index1), value2);
+ assertEquals(client.getNonce(index1), value2);
+
+ final String name2 = "name2";
+ assertEquals(server.getHandleForName(name2), INVALID_NONCE_INDEX);
+ assertEquals(client.getHandleForName(name2), INVALID_NONCE_INDEX);
+ final int index2 = server.storeName(name2);
+ assertNotEquals(index2, INVALID_NONCE_INDEX);
+ assertEquals(server.getHandleForName(name2), index2);
+ assertEquals(client.getHandleForName(name2), index2);
+ assertEquals(server.storeName(name2), index2);
+
+ // The names are different, so the indices must be different.
+ assertNotEquals(index1, index2);
+
+ shmem.close();
+ }
}
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
new file mode 100644
index 000000000000..f9b481ffc1ef
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2024 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 android.content.pm.parsing;
+
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.content.pm.PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.SuppressLint;
+import android.app.UiAutomation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+import android.util.PackageUtils;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import libcore.util.HexEncoding;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+public class ApkLiteParseUtilsTest {
+
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/coretests/pm/";
+ private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+ private static final String TEST_APP_USING_SDK1_AND_SDK1 = "HelloWorldUsingSdk1AndSdk1.apk";
+ private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
+ "HelloWorldUsingSdkMalformedNegativeVersion.apk";
+ private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
+ private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
+ private static final String TEST_SDK1_NAME = "com.test.sdk1";
+ private static final long TEST_SDK1_VERSION = 1;
+ private static final String TEST_SDK2_NAME = "com.test.sdk2";
+ private static final long TEST_SDK2_VERSION = 2;
+
+ private final PackageParser2 mPackageParser2 = new PackageParser2(
+ null, null, null, new FakePackageParser2Callback());
+
+ private File mTmpDir = null;
+
+ @Before
+ public void setUp() throws IOException {
+ mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", "invalid");
+ uninstallPackageSilently(TEST_SDK1_NAME);
+ }
+
+ @SuppressLint("CheckResult")
+ @Test
+ public void testParseApkLite_getUsesSdkLibrary() throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+
+ ApkLite baseApk = result.getResult();
+ assertThat(baseApk.getUsesSdkLibraries()).containsExactly(TEST_SDK1_NAME, TEST_SDK2_NAME);
+ assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly(
+ TEST_SDK1_VERSION, TEST_SDK2_VERSION
+ );
+ for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) {
+ assertThat(certDigests).asList().containsExactly("");
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ @Test
+ public void testParseApkLite_getUsesSdkLibrary_overrideCertDigest() throws Exception {
+ installPackage(TEST_SDK1);
+ String certDigest = getPackageCertDigest(TEST_SDK1_PACKAGE);
+ overrideUsesSdkLibraryCertificateDigest(certDigest);
+
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ ApkLite baseApk = result.getResult();
+
+ String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
+ assertThat(liteCerts).isNotNull();
+ for (String[] certDigests: liteCerts) {
+ assertThat(certDigests).asList().containsExactly(certDigest);
+ }
+
+ // Same for package parser
+ AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+ String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
+ assertThat(pkgCerts).isNotNull();
+ for (int i = 0; i < liteCerts.length; i++) {
+ assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
+ }
+ }
+
+
+ @SuppressLint("CheckResult")
+ @Test
+ public void testParseApkLite_getUsesSdkLibrary_sameAsPackageParser() throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+ ApkLite baseApk = result.getResult();
+
+ AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+ assertThat(baseApk.getUsesSdkLibraries())
+ .containsExactlyElementsIn(pkg.getUsesSdkLibraries());
+ List<Long> versionsBoxed = Arrays.stream(pkg.getUsesSdkLibrariesVersionsMajor()).boxed()
+ .toList();
+ assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList()
+ .containsExactlyElementsIn(versionsBoxed);
+
+ String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
+ String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
+ for (int i = 0; i < liteCerts.length; i++) {
+ assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ @Test
+ public void testParseApkLite_malformedUsesSdkLibrary_duplicate() throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK1);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isTrue();
+ assertThat(result.getErrorMessage()).contains("Bad uses-sdk-library declaration");
+ assertThat(result.getErrorMessage()).contains(
+ "Depending on multiple versions of SDK library");
+ }
+
+ @SuppressLint("CheckResult")
+ @Test
+ public void testParseApkLite_malformedUsesSdkLibrary_missingVersion() throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK_MALFORMED_VERSION);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isTrue();
+ assertThat(result.getErrorMessage()).contains("Bad uses-sdk-library declaration");
+ }
+
+ private String getPackageCertDigest(String packageName) throws Exception {
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ PackageInfo sdkPackageInfo = getPackageManager().getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(
+ GET_SIGNING_CERTIFICATES | MATCH_STATIC_SHARED_AND_SDK_LIBRARIES));
+ SigningInfo signingInfo = sdkPackageInfo.signingInfo;
+ Signature[] signatures =
+ signingInfo != null ? signingInfo.getSigningCertificateHistory() : null;
+ byte[] digest = PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray());
+ return new String(HexEncoding.encode(digest));
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ private static PackageManager getPackageManager() {
+ return InstrumentationRegistry.getContext().getPackageManager();
+ }
+
+ /**
+ * SDK package is signed by build system. In theory we could try to extract the signature,
+ * and patch the app manifest. This property allows us to override in runtime, which is much
+ * easier.
+ */
+ private void overrideUsesSdkLibraryCertificateDigest(String sdkCertDigest) throws Exception {
+ setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", sdkCertDigest);
+ }
+
+ private void setSystemProperty(String name, String value) throws Exception {
+ assertThat(executeShellCommand("setprop " + name + " " + value)).isEmpty();
+ }
+
+ private static String executeShellCommand(String command) throws IOException {
+ final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
+ try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+ return readFullStream(inputStream);
+ }
+ }
+
+ private static String readFullStream(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ writeFullStream(inputStream, result, -1);
+ return result.toString("UTF-8");
+ }
+
+ private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
+ long expected) throws IOException {
+ byte[] buffer = new byte[1024];
+ long total = 0;
+ int length;
+ while ((length = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, length);
+ total += length;
+ }
+ if (expected > 0) {
+ assertThat(expected).isEqualTo(total);
+ }
+ }
+
+ private static UiAutomation getUiAutomation() {
+ return InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ }
+
+ private File copyApkToTmpDir(String apkFileName) throws Exception {
+ File outFile = new File(mTmpDir, apkFileName);
+ String apkFilePath = PUSH_FILE_DIR + apkFileName;
+ File apkFile = new File(apkFilePath);
+ assertThat(apkFile.exists()).isTrue();
+ try (InputStream is = new FileInputStream(apkFile)) {
+ FileUtils.copyToFileOrThrow(is, outFile);
+ }
+ return outFile;
+ }
+
+ static String createApkPath(String baseName) {
+ return PUSH_FILE_DIR + baseName;
+ }
+
+ /* Install for all the users */
+ private void installPackage(String baseName) throws IOException {
+ File file = new File(createApkPath(baseName));
+ assertThat(executeShellCommand("pm install -t -g " + file.getPath()))
+ .isEqualTo("Success\n");
+ }
+
+ private static String uninstallPackageSilently(String packageName) throws IOException {
+ return executeShellCommand("pm uninstall " + packageName);
+ }
+
+ static class FakePackageParser2Callback extends PackageParser2.Callback {
+
+ @Override
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ return true;
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ return true;
+ }
+
+ @Override
+ public @NonNull Set<String> getHiddenApiWhitelistedApps() {
+ return new ArraySet<>();
+ }
+
+ @Override
+ public @NonNull Set<String> getInstallConstraintsAllowlist() {
+ return new ArraySet<>();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 79a478a7676b..35765a9f8e08 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -41,9 +41,26 @@ public class NotificationProgressBarTest {
List<ProgressStyle.Segment> segments = new ArrayList<>();
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_segmentsLengthNotMatchingProgressMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(50));
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 50;
+ int progressMax = 100;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -51,11 +68,14 @@ public class NotificationProgressBarTest {
public void processAndConvertToDrawableParts_segmentLengthIsNegative() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(-50));
+ segments.add(new ProgressStyle.Segment(150));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -63,11 +83,14 @@ public class NotificationProgressBarTest {
public void processAndConvertToDrawableParts_segmentLengthIsZero() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(0));
+ segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -77,9 +100,11 @@ public class NotificationProgressBarTest {
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = -50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -89,10 +114,11 @@ public class NotificationProgressBarTest {
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 0;
+ int progressMax = 100;
boolean isStyledByProgress = true;
List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, isStyledByProgress);
+ segments, points, progress, progressMax, isStyledByProgress);
int fadedRed = 0x7FFF0000;
List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true)));
@@ -106,10 +132,11 @@ public class NotificationProgressBarTest {
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 100;
+ int progressMax = 100;
boolean isStyledByProgress = true;
List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, isStyledByProgress);
+ segments, points, progress, progressMax, isStyledByProgress);
List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -122,10 +149,11 @@ public class NotificationProgressBarTest {
segments.add(new ProgressStyle.Segment(100));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 150;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
- isStyledByProgress);
+ progressMax, isStyledByProgress);
}
@Test(expected = IllegalArgumentException.class)
@@ -135,9 +163,11 @@ public class NotificationProgressBarTest {
List<ProgressStyle.Point> points = new ArrayList<>();
points.add(new ProgressStyle.Point(-50).setColor(Color.RED));
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -148,9 +178,11 @@ public class NotificationProgressBarTest {
List<ProgressStyle.Point> points = new ArrayList<>();
points.add(new ProgressStyle.Point(0).setColor(Color.RED));
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -161,9 +193,11 @@ public class NotificationProgressBarTest {
List<ProgressStyle.Point> points = new ArrayList<>();
points.add(new ProgressStyle.Point(100).setColor(Color.RED));
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -174,9 +208,11 @@ public class NotificationProgressBarTest {
List<ProgressStyle.Point> points = new ArrayList<>();
points.add(new ProgressStyle.Point(150).setColor(Color.RED));
int progress = 50;
+ int progressMax = 100;
boolean isStyledByProgress = true;
NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ progressMax,
isStyledByProgress);
}
@@ -187,10 +223,11 @@ public class NotificationProgressBarTest {
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 60;
+ int progressMax = 100;
boolean isStyledByProgress = true;
List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, isStyledByProgress);
+ segments, points, progress, progressMax, isStyledByProgress);
// Colors with 50% opacity
int fadedGreen = 0x7F00FF00;
@@ -213,6 +250,7 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
+ int progressMax = 100;
boolean isStyledByProgress = true;
// Colors with 50% opacity
@@ -231,7 +269,7 @@ public class NotificationProgressBarTest {
new Segment(0.25f, fadedBlue, true)));
List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, isStyledByProgress);
+ segments, points, progress, progressMax, isStyledByProgress);
assertThat(parts).isEqualTo(expected);
}
@@ -247,10 +285,11 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
+ int progressMax = 100;
boolean isStyledByProgress = true;
List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, isStyledByProgress);
+ segments, points, progress, progressMax, isStyledByProgress);
// Colors with 50% opacity
int fadedGreen = 0x7F00FF00;
@@ -281,10 +320,11 @@ public class NotificationProgressBarTest {
points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
int progress = 60;
+ int progressMax = 100;
boolean isStyledByProgress = false;
List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
- segments, points, progress, isStyledByProgress);
+ segments, points, progress, progressMax, isStyledByProgress);
List<Part> expected = new ArrayList<>(List.of(
new Segment(0.15f, Color.RED),
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f36dff06db98..42188dec4236 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -157,6 +157,13 @@ java_library {
}
filegroup {
+ name: "wm_shell-shared-utils",
+ srcs: [
+ "shared/src/com/android/wm/shell/shared/TransitionUtil.java",
+ ],
+}
+
+filegroup {
name: "wm_shell-shared-aidls",
srcs: [
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 73bc42687bb9..5eb5d8962b55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -95,6 +95,7 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
@@ -1277,42 +1278,42 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
// Copy initial changes to final transition
final TransitionInfo init = mOpenTransitionInfo;
- // find prepare open target
+ // Find prepare open target
boolean openShowWallpaper = false;
- ComponentName openComponent = null;
+ final ArrayList<OpenChangeInfo> targets = new ArrayList<>();
int tmpSize;
- int openTaskId = INVALID_TASK_ID;
- WindowContainerToken openToken = null;
for (int j = init.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = init.getChanges().get(j);
- if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
- openComponent = findComponentName(change);
- openTaskId = findTaskId(change);
- openToken = findToken(change);
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+ && TransitionUtil.isOpeningMode(change.getMode())) {
+ final ComponentName openComponent = findComponentName(change);
+ final int openTaskId = findTaskId(change);
+ final WindowContainerToken openToken = findToken(change);
+ if (openComponent == null && openTaskId == INVALID_TASK_ID
+ && openToken == null) {
+ continue;
+ }
+ targets.add(new OpenChangeInfo(openComponent, openTaskId, openToken));
if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
openShowWallpaper = true;
}
- break;
}
}
- if (openComponent == null && openTaskId == INVALID_TASK_ID && openToken == null) {
+ if (targets.isEmpty()) {
// This shouldn't happen, but if that happen, consume the initial transition anyway.
Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
+ "animated target from the open transition=" + mOpenTransitionInfo);
mOpenTransitionInfo = null;
return;
}
- // find first non-prepare open target
+ // Find first non-prepare open target
boolean isOpen = false;
tmpSize = info.getChanges().size();
for (int j = 0; j < tmpSize; ++j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- final ComponentName firstNonOpen = findComponentName(change);
- final int firstTaskId = findTaskId(change);
- if ((firstNonOpen != null && firstNonOpen != openComponent)
- || (firstTaskId != INVALID_TASK_ID && firstTaskId != openTaskId)) {
- // this is original close target, potential be close, but cannot determine from
- // it
+ if (isOpenChangeMatched(targets, change)) {
+ // This is original close target, potential be close, but cannot determine
+ // from it.
if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
isOpen = !TransitionUtil.isClosingMode(change.getMode());
} else {
@@ -1321,33 +1322,44 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
}
-
if (!isOpen) {
// Close transition, the transition info should be:
// init info(open A & wallpaper)
// current info(close B target)
// remove init info(open/change A target & wallpaper)
boolean moveToTop = false;
+ boolean excludeOpenTarget = false;
+ boolean mergePredictive = false;
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
+ if (isOpenChangeMatched(targets, change)) {
+ if (TransitionUtil.isClosingMode(change.getMode())) {
+ excludeOpenTarget = true;
+ }
moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
info.getChanges().remove(j);
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
|| !change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
info.getChanges().remove(j);
+ } else if (!mergePredictive && TransitionUtil.isClosingMode(change.getMode())) {
+ mergePredictive = true;
}
}
// Ignore merge if there is no close target
- if (!info.getChanges().isEmpty()) {
+ if (!info.getChanges().isEmpty() && mergePredictive) {
tmpSize = init.getChanges().size();
for (int i = 0; i < tmpSize; ++i) {
final TransitionInfo.Change change = init.getChanges().get(i);
if (change.hasFlags(FLAG_IS_WALLPAPER)) {
continue;
}
- if (moveToTop) {
- if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
+ if (isOpenChangeMatched(targets, change)) {
+ if (excludeOpenTarget) {
+ // App has triggered another change during predictive back
+ // transition, filter out predictive back target.
+ continue;
+ }
+ if (moveToTop) {
change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
}
}
@@ -1376,7 +1388,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (nonBackClose && nonBackOpen) {
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
+ if (isOpenChangeMatched(targets, change)) {
info.getChanges().remove(j);
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
info.getChanges().remove(j);
@@ -1659,9 +1671,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final ComponentName openChange = findComponentName(change);
final int firstTaskId = findTaskId(change);
final WindowContainerToken openToken = findToken(change);
- return (openChange != null && openChange == topActivity)
+ return (openChange != null && openChange.equals(topActivity))
|| (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
- || (openToken != null && token == openToken);
+ || (openToken != null && openToken.equals(token));
+ }
+
+ static boolean isOpenChangeMatched(@NonNull ArrayList<OpenChangeInfo> targets,
+ TransitionInfo.Change change) {
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ final OpenChangeInfo next = targets.get(i);
+ if (isSameChangeTarget(next.mOpenComponent, next.mOpenTaskId, next.mOpenToken,
+ change)) {
+ return true;
+ }
+ }
+ return false;
}
private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
@@ -1721,4 +1745,16 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
}
+
+ static class OpenChangeInfo {
+ final ComponentName mOpenComponent;
+ final int mOpenTaskId;
+ final WindowContainerToken mOpenToken;
+ OpenChangeInfo(ComponentName openComponent, int openTaskId,
+ WindowContainerToken openToken) {
+ mOpenComponent = openComponent;
+ mOpenTaskId = openTaskId;
+ mOpenToken = openToken;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 5998dc848e2b..6f7b7162d2ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -129,19 +129,24 @@ class DesktopRepository (
DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
?: desktop.zOrderedTasksCount
+ var visibleTasksCount = 0
desktop.zOrderedTasksList
// Reverse it so we initialize the repo from bottom to top.
.reversed()
- .mapNotNull { taskId ->
- desktop.tasksByTaskIdMap[taskId]?.takeIf {
- it.desktopTaskState == DesktopTaskState.VISIBLE
- }
- }
- .take(maxTasks)
+ .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
.forEach { task ->
- addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
- addActiveTask(desktop.displayId, task.taskId)
- updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+ if (task.desktopTaskState == DesktopTaskState.VISIBLE
+ && visibleTasksCount < maxTasks
+ ) {
+ visibleTasksCount++
+ addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
+ addActiveTask(desktop.displayId, task.taskId)
+ updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+ } else {
+ addActiveTask(desktop.displayId, task.taskId)
+ updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+ minimizeTask(desktop.displayId, task.taskId)
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 77fb4b4815f1..bc78e43a15ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -373,7 +373,7 @@ class DesktopTasksController(
}
logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
// TODO(342378842): Instead of using default display, support multiple displays
- val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
+ val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
DEFAULT_DISPLAY, wct, taskId)
val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -388,7 +388,7 @@ class DesktopTasksController(
)
// TODO(343149901): Add DPI changes for task launch
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
- addPendingMinimizeTransition(transition, taskToMinimize)
+ taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
runOnTransit?.invoke(transition)
return true
}
@@ -412,12 +412,12 @@ class DesktopTasksController(
excludeTaskId = task.taskId,
)
// Bring other apps to front first
- val taskToMinimize =
+ val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
addMoveToDesktopChanges(wct, task)
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
- addPendingMinimizeTransition(transition, taskToMinimize)
+ taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
runOnTransit?.invoke(transition)
}
@@ -452,14 +452,14 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
moveHomeTask(wct, toTop = true)
- val taskToMinimize =
+ val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
wct, taskInfo.displayId)
val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
transition?.let {
- addPendingMinimizeTransition(it, taskToMinimize)
+ taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
runOnTransit?.invoke(transition)
}
}
@@ -651,7 +651,13 @@ class DesktopTasksController(
excludeTaskId = taskInfo.taskId,
)
val transition =
- startLaunchTransition(TRANSIT_TO_FRONT, wct, taskInfo.taskId, remoteTransition)
+ startLaunchTransition(
+ TRANSIT_TO_FRONT,
+ wct,
+ taskInfo.taskId,
+ remoteTransition,
+ taskInfo.displayId
+ )
runOnTransit?.invoke(transition)
}
@@ -660,15 +666,15 @@ class DesktopTasksController(
wct: WindowContainerTransaction,
taskId: Int,
remoteTransition: RemoteTransition?,
+ displayId: Int = DEFAULT_DISPLAY,
): IBinder {
- val taskToMinimize: RunningTaskInfo? =
- addAndGetMinimizeChanges(DEFAULT_DISPLAY, wct, taskId)
+ val taskIdToMinimize = addAndGetMinimizeChanges(displayId, wct, taskId)
if (remoteTransition == null) {
val t = transitions.startTransition(transitionType, wct, null /* handler */)
- addPendingMinimizeTransition(t, taskToMinimize)
+ taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
return t
}
- if (taskToMinimize == null) {
+ if (taskIdToMinimize == null) {
val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
@@ -676,10 +682,10 @@ class DesktopTasksController(
}
val remoteTransitionHandler =
DesktopWindowLimitRemoteHandler(
- mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskToMinimize.taskId)
+ mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
- addPendingMinimizeTransition(t, taskToMinimize)
+ taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
return t
}
@@ -1032,13 +1038,13 @@ class DesktopTasksController(
displayId: Int,
wct: WindowContainerTransaction,
newTaskIdInFront: Int
- ): RunningTaskInfo? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
+ ): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
private fun bringDesktopAppsToFront(
displayId: Int,
wct: WindowContainerTransaction,
newTaskIdInFront: Int? = null
- ): RunningTaskInfo? {
+ ): Int? {
logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
// Move home to front, ensures that we go back home when all desktop windows are closed
moveHomeTask(wct, toTop = true)
@@ -1054,11 +1060,10 @@ class DesktopTasksController(
taskRepository.getExpandedTasksOrdered(displayId)
// If we're adding a new Task we might need to minimize an old one
// TODO(b/365725441): Handle non running task minimization
- val taskToMinimize: RunningTaskInfo? =
+ val taskIdToMinimize: Int? =
if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
- desktopTasksLimiter
- .get()
- .getTaskToMinimize(
+ desktopTasksLimiter.get()
+ .getTaskIdToMinimize(
expandedTasksOrderedFrontToBack,
newTaskIdInFront
)
@@ -1068,7 +1073,7 @@ class DesktopTasksController(
expandedTasksOrderedFrontToBack
// If there is a Task to minimize, let it stay behind the Home Task
- .filter { taskId -> taskId != taskToMinimize?.taskId }
+ .filter { taskId -> taskId != taskIdToMinimize }
.reversed() // Start from the back so the front task is brought forward last
.forEach { taskId ->
val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)
@@ -1089,7 +1094,7 @@ class DesktopTasksController(
taskbarDesktopTaskListener?.
onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
- return taskToMinimize
+ return taskIdToMinimize
}
private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
@@ -1341,7 +1346,7 @@ class DesktopTasksController(
val options = createNewWindowOptions(callingTask)
if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
wct.startTask(requestedTaskId, options.toBundle())
- val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
+ val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
callingTask.displayId, wct, requestedTaskId)
val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -1349,7 +1354,7 @@ class DesktopTasksController(
excludeTaskId = requestedTaskId,
)
val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
- addPendingMinimizeTransition(transition, taskToMinimize)
+ taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
runOnTransit?.invoke(transition)
} else {
val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
@@ -1489,9 +1494,9 @@ class DesktopTasksController(
// 1) Exit immersive if needed.
desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId)
// 2) minimize a Task if needed.
- val taskToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
- if (taskToMinimize != null) {
- addPendingMinimizeTransition(transition, taskToMinimize)
+ val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+ if (taskIdToMinimize != null) {
+ addPendingMinimizeTransition(transition, taskIdToMinimize)
return wct
}
return if (wct.isEmpty) null else wct
@@ -1515,9 +1520,8 @@ class DesktopTasksController(
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
- val taskToMinimize =
- addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
- addPendingMinimizeTransition(transition, taskToMinimize)
+ val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+ taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
desktopImmersiveController.exitImmersiveIfApplicable(
transition, wct, task.displayId
)
@@ -1683,7 +1687,7 @@ class DesktopTasksController(
displayId: Int,
wct: WindowContainerTransaction,
newTaskId: Int
- ): RunningTaskInfo? {
+ ): Int? {
if (!desktopTasksLimiter.isPresent) return null
return desktopTasksLimiter
.get()
@@ -1692,9 +1696,9 @@ class DesktopTasksController(
private fun addPendingMinimizeTransition(
transition: IBinder,
- taskToMinimize: RunningTaskInfo?
+ taskIdToMinimize: Int,
) {
- if (taskToMinimize == null) return
+ val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
desktopTasksLimiter.ifPresent {
it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index cd28a4fa67ec..7bcc5d1691aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.desktopmode
-import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.os.Handler
import android.os.IBinder
@@ -190,17 +189,19 @@ class DesktopTasksLimiter (
displayId: Int,
wct: WindowContainerTransaction,
newFrontTaskId: Int,
- ): RunningTaskInfo? {
+ ): Int? {
logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId)
- // This list is ordered from front to back.
- val newTaskOrderedList = createOrderedTaskListWithNewTask(
- taskRepository.getExpandedTasksOrdered(displayId), newFrontTaskId)
- val taskToMinimize = getTaskToMinimize(newTaskOrderedList)
- if (taskToMinimize != null) {
- wct.reorder(taskToMinimize.token, false /* onTop */)
- return taskToMinimize
+
+ val taskIdToMinimize =
+ getTaskIdToMinimize(
+ taskRepository.getExpandedTasksOrdered(displayId),
+ newFrontTaskId
+ )
+ // If it's a running task, reorder it to back.
+ taskIdToMinimize?.let { shellTaskOrganizer.getRunningTaskInfo(it) }?.let {
+ wct.reorder(it.token, false /* onTop */)
}
- return null
+ return taskIdToMinimize
}
/**
@@ -216,31 +217,33 @@ class DesktopTasksLimiter (
* Returns the minimized task from the list of visible tasks ordered from front to back with
* the new task placed in front of other tasks.
*/
- fun getTaskToMinimize(
+ fun getTaskIdToMinimize(
visibleOrderedTasks: List<Int>,
- newTaskIdInFront: Int
- ): RunningTaskInfo? =
- getTaskToMinimize(createOrderedTaskListWithNewTask(visibleOrderedTasks, newTaskIdInFront))
+ newTaskIdInFront: Int? = null
+ ): Int? {
+ return getTaskIdToMinimize(
+ createOrderedTaskListWithGivenTaskInFront(
+ visibleOrderedTasks, newTaskIdInFront))
+ }
/** Returns the Task to minimize given a list of visible tasks ordered from front to back. */
- fun getTaskToMinimize(visibleOrderedTasks: List<Int>): RunningTaskInfo? {
+ private fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>): Int? {
if (visibleOrderedTasks.size <= maxTasksLimit) {
logV("No need to minimize; tasks below limit")
+ // No need to minimize anything
return null
}
- val taskIdToMinimize = visibleOrderedTasks.last()
- val taskToMinimize =
- shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
- if (taskToMinimize == null) {
- logE("taskToMinimize(taskId = %d) == null", taskIdToMinimize)
- return null
- }
- return taskToMinimize
+ return visibleOrderedTasks.last()
}
- private fun createOrderedTaskListWithNewTask(
- orderedTaskIds: List<Int>, newTaskId: Int): List<Int> =
- listOf(newTaskId) + orderedTaskIds.filter { taskId -> taskId != newTaskId }
+ private fun createOrderedTaskListWithGivenTaskInFront(
+ existingTaskIdsOrderedFrontToBack: List<Int>,
+ newTaskId: Int?
+ ): List<Int> {
+ return if (newTaskId == null) existingTaskIdsOrderedFrontToBack
+ else listOf(newTaskId) +
+ existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId }
+ }
@VisibleForTesting
fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079b7fba..8ac7f89d8f8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -476,6 +476,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
mPipTaskOrganizer.removePip();
mTvPipMenuController.closeMenu();
+ mPipNotificationController.dismiss();
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 6b62adb34033..72d4dc6ffac9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -51,6 +51,7 @@ import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -812,7 +813,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
if (taskId != INVALID_TASK_ID) {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = taskId;
- taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+ final IWindowContainerToken mockT = mock(IWindowContainerToken.class);
+ Binder binder = new Binder();
+ doReturn(binder).when(mockT).asBinder();
+ taskInfo.token = new WindowContainerToken(mockT);
change = new TransitionInfo.Change(
taskInfo.token, b.build());
change.setTaskInfo(taskInfo);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index fa878d0bb077..4d7e47fa51bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -329,7 +329,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
wct = wct,
newFrontTaskId = setUpFreeformTask().taskId)
- assertThat(minimizedTaskId).isEqualTo(tasks.first())
+ assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId)
assertThat(wct.hierarchyOps.size).isEqualTo(1)
assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
assertThat(wct.hierarchyOps[0].toTop).isFalse() // Reorder to bottom
@@ -355,7 +355,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+ val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
visibleOrderedTasks = tasks.map { it.taskId })
assertThat(minimizedTask).isNull()
@@ -365,11 +365,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() {
val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+ val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
visibleOrderedTasks = tasks.map { it.taskId })
// first == front, last == back
- assertThat(minimizedTask).isEqualTo(tasks.last())
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
}
@Test
@@ -379,23 +379,23 @@ class DesktopTasksLimiterTest : ShellTestCase() {
interactionJankMonitor, mContext, handler)
val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+ val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
visibleOrderedTasks = tasks.map { it.taskId })
// first == front, last == back
- assertThat(minimizedTask).isEqualTo(tasks.last())
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
}
@Test
fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() {
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+ val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
visibleOrderedTasks = tasks.map { it.taskId },
newTaskIdInFront = setUpFreeformTask().taskId)
// first == front, last == back
- assertThat(minimizedTask).isEqualTo(tasks.last())
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
}
@Test
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 0320aabd7199..8253c1f8f0b3 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -11265,4 +11265,15 @@
column="24"/>
</issue>
-</issues> \ No newline at end of file
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="INetworkScoreCache permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java"
+ line="250"
+ column="9"/>
+ </issue>
+
+</issues>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c4b50a08802b..44aa0b2d4a41 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -33,6 +33,13 @@ flag {
}
flag {
+ name: "notifications_redesign_footer_view"
+ namespace: "systemui"
+ description: "Notifications Redesign: Update the look of the notifications footer."
+ bug: "375010573"
+}
+
+flag {
name: "notification_row_content_binder_refactor"
namespace: "systemui"
description: "Convert the NotificationContentInflater to Kotlin and restructure it to support modern views"
@@ -1553,6 +1560,16 @@ flag {
}
flag {
+ name: "transition_race_condition"
+ namespace: "systemui"
+ description: "Thread-safe keyguard transitions"
+ bug: "358533338"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "media_projection_request_attribution_fix"
namespace: "systemui"
description: "Ensure MediaProjection consent requests are properly attributed"
diff --git a/packages/SystemUI/animation/lib/Android.bp b/packages/SystemUI/animation/lib/Android.bp
index d9230ec67461..7d7302309af2 100644
--- a/packages/SystemUI/animation/lib/Android.bp
+++ b/packages/SystemUI/animation/lib/Android.bp
@@ -48,6 +48,19 @@ java_library {
}
filegroup {
+ name: "PlatformAnimationLib-client-srcs",
+ srcs: [
+ "src/com/android/systemui/animation/OriginRemoteTransition.java",
+ "src/com/android/systemui/animation/OriginTransitionSession.java",
+ "src/com/android/systemui/animation/SurfaceUIComponent.java",
+ "src/com/android/systemui/animation/Transactions.java",
+ "src/com/android/systemui/animation/UIComponent.java",
+ "src/com/android/systemui/animation/ViewUIComponent.java",
+ ":PlatformAnimationLib-aidl",
+ ],
+}
+
+filegroup {
name: "PlatformAnimationLib-aidl",
srcs: [
"src/**/*.aidl",
diff --git a/packages/SystemUI/animation/lib/OWNERS b/packages/SystemUI/animation/lib/OWNERS
new file mode 100644
index 000000000000..7569419bf4a4
--- /dev/null
+++ b/packages/SystemUI/animation/lib/OWNERS
@@ -0,0 +1,3 @@
+#inherits OWNERS from SystemUI in addition to WEAR framework owners below
+file:platform/frameworks/base:/WEAR_OWNERS
+
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
index 08db95e5a795..2b5ff7c4b598 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -44,6 +44,7 @@ import java.util.List;
/**
* An implementation of {@link IRemoteTransition} that accepts a {@link UIComponent} as the origin
* and automatically attaches it to the transition leash before the transition starts.
+ * @hide
*/
public class OriginRemoteTransition extends IRemoteTransition.Stub {
private static final String TAG = "OriginRemoteTransition";
@@ -328,7 +329,9 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
/* baseBounds= */ maxBounds);
}
- /** An interface that represents an origin transitions. */
+ /** An interface that represents an origin transitions.
+ * @hide
+ */
public interface TransitionPlayer {
/**
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
index 23693b68a920..6d6aa8895ed0 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
@@ -43,6 +43,7 @@ import java.util.function.Supplier;
/**
* A session object that holds origin transition states for starting an activity from an on-screen
* UI component and smoothly transitioning back from the activity to the same UI component.
+ * @hide
*/
public class OriginTransitionSession {
private static final String TAG = "OriginTransitionSession";
@@ -179,7 +180,10 @@ public class OriginTransitionSession {
}
}
- /** A builder to build a {@link OriginTransitionSession}. */
+ /**
+ * A builder to build a {@link OriginTransitionSession}.
+ * @hide
+ */
public static class Builder {
private final Context mContext;
@Nullable private final IOriginTransitions mOriginTransitions;
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
index 24387360936b..e1ff2a4c8208 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
@@ -26,7 +26,10 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Executor;
-/** A {@link UIComponent} representing a {@link SurfaceControl}. */
+/**
+ * A {@link UIComponent} representing a {@link SurfaceControl}.
+ * @hide
+ */
public class SurfaceUIComponent implements UIComponent {
private final Collection<SurfaceControl> mSurfaces;
private final Rect mBaseBounds;
@@ -89,7 +92,10 @@ public class SurfaceUIComponent implements UIComponent {
+ "}";
}
- /** A {@link Transaction} wrapping a {@link SurfaceControl.Transaction}. */
+ /**
+ * A {@link Transaction} wrapping a {@link SurfaceControl.Transaction}.
+ * @hide
+ */
public static class Transaction implements UIComponent.Transaction<SurfaceUIComponent> {
private final SurfaceControl.Transaction mTransaction;
private final ArrayList<Runnable> mChanges = new ArrayList<>();
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
index 5240d99a9217..64d21724ba32 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
@@ -27,6 +27,7 @@ import java.util.concurrent.Executor;
/**
* A composite {@link UIComponent.Transaction} that combines multiple other transactions for each ui
* type.
+ * @hide
*/
public class Transactions implements UIComponent.Transaction<UIComponent> {
private final Map<Class, UIComponent.Transaction> mTransactions = new ArrayMap<>();
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
index 747e4d1eb278..8aec2f42a5f1 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
@@ -17,12 +17,17 @@
package com.android.systemui.animation;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.view.SurfaceControl;
import java.util.concurrent.Executor;
-/** An interface representing an UI component on the display. */
+/**
+ * An interface representing an UI component on the display.
+ * @hide
+ */
public interface UIComponent {
/** Get the current alpha of this UI. */
@@ -32,39 +37,48 @@ public interface UIComponent {
boolean isVisible();
/** Get the bounds of this UI in its display. */
+ @NonNull
Rect getBounds();
/** Create a new {@link Transaction} that can update this UI. */
+ @NonNull
Transaction newTransaction();
/**
* A transaction class for updating {@link UIComponent}.
*
* @param <T> the subtype of {@link UIComponent} that this {@link Transaction} can handle.
+ * @hide
*/
interface Transaction<T extends UIComponent> {
/** Update alpha of an UI. Execution will be delayed until {@link #commit()} is called. */
- Transaction setAlpha(T ui, @FloatRange(from = 0.0, to = 1.0) float alpha);
+ Transaction setAlpha(@NonNull T ui, @FloatRange(from = 0.0, to = 1.0) float alpha);
/**
* Update visibility of an UI. Execution will be delayed until {@link #commit()} is called.
*/
- Transaction setVisible(T ui, boolean visible);
+ @NonNull
+ Transaction setVisible(@NonNull T ui, boolean visible);
/** Update bounds of an UI. Execution will be delayed until {@link #commit()} is called. */
- Transaction setBounds(T ui, Rect bounds);
+ @NonNull
+ Transaction setBounds(@NonNull T ui, @NonNull Rect bounds);
/**
* Attach a ui to the transition leash. Execution will be delayed until {@link #commit()} is
* called.
*/
- Transaction attachToTransitionLeash(T ui, SurfaceControl transitionLeash, int w, int h);
+ @NonNull
+ Transaction attachToTransitionLeash(
+ @NonNull T ui, @NonNull SurfaceControl transitionLeash, int w, int h);
/**
* Detach a ui from the transition leash. Execution will be delayed until {@link #commit} is
* called.
*/
- Transaction detachFromTransitionLeash(T ui, Executor executor, Runnable onDone);
+ @NonNull
+ Transaction detachFromTransitionLeash(
+ @NonNull T ui, @NonNull Executor executor, @Nullable Runnable onDone);
/** Commit any pending changes added to this transaction. */
void commit();
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 313789c4ca7e..4c047d589a66 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -38,6 +38,7 @@ import java.util.concurrent.Executor;
* be changed to INVISIBLE in its view tree. This allows the {@link View} to transform in the
* full-screen size leash without being constrained by the view tree's boundary or inheriting its
* parent's alpha and transformation.
+ * @hide
*/
public class ViewUIComponent implements UIComponent {
private static final String TAG = "ViewUIComponent";
@@ -234,6 +235,9 @@ public class ViewUIComponent implements UIComponent {
mView.post(this::draw);
}
+ /**
+ * @hide
+ */
public static class Transaction implements UIComponent.Transaction<ViewUIComponent> {
private final List<Runnable> mChanges = new ArrayList<>();
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
index cd2dd04568a7..47ad0b3e0cd4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
@@ -95,16 +95,18 @@ abstract class FlingOnBackAnimationCallback(
final override fun onBackProgressed(backEvent: BackEvent) {
val interpolatedProgress = progressInterpolator.getInterpolation(backEvent.progress)
if (predictiveBackTimestampApi()) {
- velocityTracker.addMovement(
- MotionEvent.obtain(
- /* downTime */ downTime!!,
- /* eventTime */ backEvent.frameTimeMillis,
- /* action */ ACTION_MOVE,
- /* x */ interpolatedProgress * SCALE_FACTOR,
- /* y */ 0f,
- /* metaState */ 0,
+ downTime?.let { downTime ->
+ velocityTracker.addMovement(
+ MotionEvent.obtain(
+ /* downTime */ downTime,
+ /* eventTime */ backEvent.frameTimeMillis,
+ /* action */ ACTION_MOVE,
+ /* x */ interpolatedProgress * SCALE_FACTOR,
+ /* y */ 0f,
+ /* metaState */ 0,
+ )
)
- )
+ }
lastBackEvent =
BackEvent(
backEvent.touchX,
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index 6fc13d7e0ddc..91dc3e338c59 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -22,20 +22,13 @@ package {
default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
}
-android_library {
-
+java_library {
name: "SystemUICommon",
- use_resource_processor: true,
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
- static_libs: [
- "androidx.core_core-ktx",
- ],
-
- manifest: "AndroidManifest.xml",
kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SystemUI/common/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml
deleted file mode 100644
index 6f757eb67d2e..000000000000
--- a/packages/SystemUI/common/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.common">
-
-</manifest>
diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md
index 1cc5277aa83e..17e541266965 100644
--- a/packages/SystemUI/common/README.md
+++ b/packages/SystemUI/common/README.md
@@ -1,5 +1,9 @@
# SystemUICommon
-`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies.
+`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended
+to be used by other modules, and therefore should not have other SystemUI dependencies to avoid
+circular dependencies.
-To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate.
+To maintain the structure of this module, please refrain from adding components at the top level.
+Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to
+keep the module organized and easy to navigate.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index e4c60e166fd5..5cb45e5bd914 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -18,9 +18,11 @@ package com.android.systemui.notifications.ui.composable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceAtMost
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.ScrollController
/**
* A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
@@ -58,33 +60,40 @@ fun NotificationScrimNestedScrollConnection(
offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
},
canStartPostFling = { false },
- canStopOnPreFling = { false },
- onStart = { offsetAvailable -> onStart(offsetAvailable) },
- onScroll = { offsetAvailable, _ ->
- val currentHeight = scrimOffset()
- val amountConsumed =
- if (offsetAvailable > 0) {
- val amountLeft = maxScrimOffset - currentHeight
- offsetAvailable.fastCoerceAtMost(amountLeft)
- } else {
- val amountLeft = minScrimOffset() - currentHeight
- offsetAvailable.fastCoerceAtLeast(amountLeft)
+ onStart = { firstScroll ->
+ onStart(firstScroll)
+ object : ScrollController {
+ override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+ val currentHeight = scrimOffset()
+ val amountConsumed =
+ if (deltaScroll > 0) {
+ val amountLeft = maxScrimOffset - currentHeight
+ deltaScroll.fastCoerceAtMost(amountLeft)
+ } else {
+ val amountLeft = minScrimOffset() - currentHeight
+ deltaScroll.fastCoerceAtLeast(amountLeft)
+ }
+ snapScrimOffset(currentHeight + amountConsumed)
+ return amountConsumed
}
- snapScrimOffset(currentHeight + amountConsumed)
- amountConsumed
- },
- onStop = { velocityAvailable ->
- onStop(velocityAvailable)
- if (scrimOffset() < minScrimOffset()) {
- animateScrimOffset(minScrimOffset())
- }
- // Don't consume the velocity on pre/post fling
- 0f
- },
- onCancel = {
- onStop(0f)
- if (scrimOffset() < minScrimOffset()) {
- animateScrimOffset(minScrimOffset())
+
+ override suspend fun onStop(initialVelocity: Float): Float {
+ onStop(initialVelocity)
+ if (scrimOffset() < minScrimOffset()) {
+ animateScrimOffset(minScrimOffset())
+ }
+ // Don't consume the velocity on pre/post fling
+ return 0f
+ }
+
+ override fun onCancel() {
+ onStop(0f)
+ if (scrimOffset() < minScrimOffset()) {
+ animateScrimOffset(minScrimOffset())
+ }
+ }
+
+ override fun canStopOnPreFling() = false
}
},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index edb05ebd77d1..e1b74a968caa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
@@ -30,6 +31,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceAtLeast
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.ScrollController
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.tanh
@@ -92,20 +94,29 @@ fun NotificationStackNestedScrollConnection(
offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
},
canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
- canStopOnPreFling = { false },
- onStart = { offsetAvailable -> onStart(offsetAvailable) },
- onScroll = { offsetAvailable, _ ->
- val minOffset = 0f
- val consumed = offsetAvailable.fastCoerceAtLeast(minOffset - stackOffset())
- if (consumed != 0f) {
- onScroll(consumed)
+ onStart = { firstScroll ->
+ onStart(firstScroll)
+ object : ScrollController {
+ override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+ val minOffset = 0f
+ val consumed = deltaScroll.fastCoerceAtLeast(minOffset - stackOffset())
+ if (consumed != 0f) {
+ onScroll(consumed)
+ }
+ return consumed
+ }
+
+ override suspend fun onStop(initialVelocity: Float): Float {
+ onStop(initialVelocity)
+ return initialVelocity
+ }
+
+ override fun onCancel() {
+ onStop(0f)
+ }
+
+ override fun canStopOnPreFling() = false
}
- consumed
- },
- onStop = { velocityAvailable ->
- onStop(velocityAvailable)
- velocityAvailable
},
- onCancel = { onStop(0f) },
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 8469007eddb6..7c7202a5c7f2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -20,6 +20,7 @@ package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.util.fastCoerceIn
@@ -27,6 +28,7 @@ import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.ScrollController
import kotlin.math.absoluteValue
internal typealias SuspendedValue<T> = suspend () -> T
@@ -66,6 +68,7 @@ internal class DraggableHandlerImpl(
internal val orientation: Orientation,
) : DraggableHandler {
internal val nestedScrollKey = Any()
+
/** The [DraggableHandler] can only have one active [DragController] at a time. */
private var dragController: DragControllerImpl? = null
@@ -345,6 +348,7 @@ private class DragControllerImpl(
distance == DistanceUnspecified ||
swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
desiredOffset
+
distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
else -> desiredOffset.fastCoerceIn(distance, 0f)
}
@@ -545,6 +549,7 @@ internal class Swipes(
upOrLeftResult == null && downOrRightResult == null -> null
(directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
upOrLeftResult
+
else -> downOrRightResult
}
}
@@ -608,7 +613,6 @@ internal class NestedScrollHandlerImpl(
return overscrollSpec != null
}
- var dragController: DragController? = null
var isIntercepting = false
return PriorityNestedScrollConnection(
@@ -669,10 +673,12 @@ internal class NestedScrollHandlerImpl(
canChangeScene = isZeroOffset
isZeroOffset && hasNextScene(offsetAvailable)
}
+
NestedScrollBehavior.EdgeWithPreview -> {
canChangeScene = isZeroOffset
hasNextScene(offsetAvailable)
}
+
NestedScrollBehavior.EdgeAlways -> {
canChangeScene = true
hasNextScene(offsetAvailable)
@@ -710,53 +716,56 @@ internal class NestedScrollHandlerImpl(
canStart
},
- // We need to maintain scroll priority even if the scene transition can no longer
- // consume the scroll gesture to allow us to return to the previous scene.
- canStopOnScroll = { _, _ -> false },
- canStopOnPreFling = { true },
- onStart = { offsetAvailable ->
+ onStart = { firstScroll ->
val pointersInfo = pointersInfo()
- dragController =
- draggableHandler.onDragStarted(
- pointersDown = pointersInfo.pointersDown,
- startedPosition = pointersInfo.startedPosition,
- overSlop = if (isIntercepting) 0f else offsetAvailable,
- )
+ scrollController(
+ dragController =
+ draggableHandler.onDragStarted(
+ pointersDown = pointersInfo.pointersDown,
+ startedPosition = pointersInfo.startedPosition,
+ overSlop = if (isIntercepting) 0f else firstScroll,
+ ),
+ canChangeScene = canChangeScene,
+ pointersInfoOwner = pointersInfoOwner,
+ )
},
- onScroll = { offsetAvailable, _ ->
- val controller = dragController ?: error("Should be called after onStart")
+ )
+ }
+}
- val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection 0f
- }
+private fun scrollController(
+ dragController: DragController,
+ canChangeScene: Boolean,
+ pointersInfoOwner: PointersInfoOwner,
+): ScrollController {
+ return object : ScrollController {
+ override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+ val pointersInfo = pointersInfoOwner.pointersInfo()
+ if (pointersInfo.isMouseWheel) {
+ // Do not support mouse wheel interactions
+ return 0f
+ }
- // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
- // initiated in a nested child.
- controller.onDrag(delta = offsetAvailable)
- },
- onStop = { velocityAvailable ->
- val controller = dragController ?: error("Should be called after onStart")
- try {
- controller
- .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene)
- .invoke()
- } finally {
- // onStop might still be running when a new gesture begins.
- // To prevent conflicts, we should only remove the drag controller if it's the
- // same one that was active initially.
- if (dragController == controller) {
- dragController = null
- }
- }
- },
- onCancel = {
- val controller = dragController ?: error("Should be called after onStart")
- controller.onStop(velocity = 0f, canChangeContent = canChangeScene)
- dragController = null
- },
- )
+ return dragController.onDrag(delta = deltaScroll)
+ }
+
+ override suspend fun onStop(initialVelocity: Float): Float {
+ return dragController
+ .onStop(velocity = initialVelocity, canChangeContent = canChangeScene)
+ .invoke()
+ }
+
+ override fun onCancel() {
+ dragController.onStop(velocity = 0f, canChangeContent = canChangeScene)
+ }
+
+ /**
+ * We need to maintain scroll priority even if the scene transition can no longer consume
+ * the scroll gesture to allow us to return to the previous scene.
+ */
+ override fun canCancelScroll(available: Float, consumed: Float) = false
+
+ override fun canStopOnPreFling() = true
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 8a0e4627d10c..fbd1cd542c05 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -128,10 +128,10 @@ private class NestedScrollToSceneNode(
) : DelegatingNode() {
private var scrollBehaviorOwner: ScrollBehaviorOwner? = null
- private fun requireScrollBehaviorOwner(): ScrollBehaviorOwner {
+ private fun findScrollBehaviorOwner(): ScrollBehaviorOwner? {
var behaviorOwner = scrollBehaviorOwner
if (behaviorOwner == null) {
- behaviorOwner = requireScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
+ behaviorOwner = findScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
scrollBehaviorOwner = behaviorOwner
}
return behaviorOwner
@@ -156,8 +156,8 @@ private class NestedScrollToSceneNode(
// transition between scenes. We can assume that the behavior is only needed if
// there is some remaining amount.
if (available != Offset.Zero) {
- requireScrollBehaviorOwner()
- .updateScrollBehaviors(
+ findScrollBehaviorOwner()
+ ?.updateScrollBehaviors(
topOrLeftBehavior = topOrLeftBehavior,
bottomOrRightBehavior = bottomOrRightBehavior,
isExternalOverscrollGesture = isExternalOverscrollGesture,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index a3f2a434cff7..fdf01cce396b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -172,15 +172,12 @@ private class SwipeToSceneNode(
}
/** Find the [ScrollBehaviorOwner] for the current orientation. */
-internal fun DelegatableNode.requireScrollBehaviorOwner(
+internal fun DelegatableNode.findScrollBehaviorOwner(
draggableHandler: DraggableHandlerImpl
-): ScrollBehaviorOwner {
- val ancestorNode =
- checkNotNull(findNearestAncestor(draggableHandler.nestedScrollKey)) {
- "This should never happen! Couldn't find a ScrollBehaviorOwner. " +
- "Are we inside an SceneTransitionLayout?"
- }
- return ancestorNode as ScrollBehaviorOwner
+): ScrollBehaviorOwner? {
+ // If there are no scenes in a particular orientation, the corresponding ScrollBehaviorOwnerNode
+ // is removed from the composition.
+ return findNearestAncestor(draggableHandler.nestedScrollKey) as? ScrollBehaviorOwner
}
internal fun interface ScrollBehaviorOwner {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index ecf64b771d1f..255da31719f3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -18,6 +18,7 @@ package com.android.compose.nestedscroll
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceAtMost
@@ -54,23 +55,38 @@ fun LargeTopAppBarNestedScrollConnection(
offsetAvailable > 0 && height() < maxHeight()
},
canStartPostFling = { false },
- canStopOnPreFling = { false },
- onStart = { /* do nothing */ },
- onScroll = { offsetAvailable, _ ->
- val currentHeight = height()
- val amountConsumed =
- if (offsetAvailable > 0) {
- val amountLeft = maxHeight() - currentHeight
- offsetAvailable.fastCoerceAtMost(amountLeft)
- } else {
- val amountLeft = minHeight() - currentHeight
- offsetAvailable.fastCoerceAtLeast(amountLeft)
- }
- onHeightChanged(currentHeight + amountConsumed)
- amountConsumed
- },
- // Don't consume the velocity on pre/post fling
- onStop = { 0f },
- onCancel = { /* do nothing */ },
+ onStart = { LargeTopAppBarScrollController(height, maxHeight, minHeight, onHeightChanged) },
)
}
+
+private class LargeTopAppBarScrollController(
+ val height: () -> Float,
+ val maxHeight: () -> Float,
+ val minHeight: () -> Float,
+ val onHeightChanged: (Float) -> Unit,
+) : ScrollController {
+ override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+ val currentHeight = height()
+ val amountConsumed =
+ if (deltaScroll > 0) {
+ val amountLeft = maxHeight() - currentHeight
+ deltaScroll.fastCoerceAtMost(amountLeft)
+ } else {
+ val amountLeft = minHeight() - currentHeight
+ deltaScroll.fastCoerceAtLeast(amountLeft)
+ }
+ onHeightChanged(currentHeight + amountConsumed)
+ return amountConsumed
+ }
+
+ override suspend fun onStop(initialVelocity: Float): Float {
+ // Don't consume the velocity on pre/post fling
+ return 0f
+ }
+
+ override fun onCancel() {
+ // do nothing
+ }
+
+ override fun canStopOnPreFling() = false
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 57d236be40ce..ca44a5c21cab 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -16,46 +16,106 @@
package com.android.compose.nestedscroll
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.animateDecay
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import com.android.compose.ui.util.SpaceVectorConverter
-import kotlin.math.abs
import kotlin.math.sign
-import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
/**
- * A [NestedScrollConnection] that intercepts scroll events in priority mode.
+ * The [ScrollController] provides control over the scroll gesture. It allows you to:
+ * - Scroll the content by a given pixel amount.
+ * - Cancel the current scroll operation.
+ * - Stop the scrolling with a given initial velocity.
*
- * Priority mode allows this connection to take control over scroll events within a nested scroll
- * hierarchy. When in priority mode, this connection consumes scroll events before its children,
- * enabling custom scrolling behaviors like sticky headers.
+ * **Important Notes:**
+ * - [onCancel] is called only when [PriorityNestedScrollConnection.reset] is invoked or when
+ * [canCancelScroll] returns `true` after a call to [onScroll]. It is never called after [onStop].
+ * - [onStop] can be interrupted by a new gesture. In such cases, you need to handle a potential
+ * cancellation within your implementation of [onStop], although [onCancel] will not be called.
+ */
+interface ScrollController {
+ /**
+ * Scrolls the current content by [deltaScroll] pixels.
+ *
+ * @param deltaScroll The amount of pixels to scroll by.
+ * @param source The source of the scroll event.
+ * @return The amount of [deltaScroll] that was consumed.
+ */
+ fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float
+
+ /**
+ * Checks if the current scroll operation can be canceled. This is typically called after
+ * [onScroll] to determine if the [ScrollController] has lost priority and should cancel the
+ * ongoing scroll operation.
+ *
+ * @param available The total amount of scroll available.
+ * @param consumed The amount of scroll consumed by [onScroll].
+ * @return `true` if the scroll can be canceled.
+ */
+ fun canCancelScroll(available: Float, consumed: Float): Boolean {
+ return consumed == 0f
+ }
+
+ /**
+ * Cancels the current scroll operation. This method is called when
+ * [PriorityNestedScrollConnection.reset] is invoked or when [canCancelScroll] returns `true`.
+ */
+ fun onCancel()
+
+ /**
+ * Checks if the scroll can be stopped during the [NestedScrollConnection.onPreFling] phase.
+ *
+ * @return `true` if the scroll can be stopped.
+ */
+ fun canStopOnPreFling(): Boolean
+
+ /**
+ * Stops the controller with the given [initialVelocity]. This typically starts a decay
+ * animation to smoothly bring the scrolling to a stop. This method can be interrupted by a new
+ * gesture, requiring you to handle potential cancellation within your implementation.
+ *
+ * @param initialVelocity The initial velocity of the scroll when stopping.
+ * @return The consumed [initialVelocity] when the animation completes.
+ */
+ suspend fun onStop(initialVelocity: Float): Float
+}
+
+/**
+ * A [NestedScrollConnection] that lets you implement custom scroll behaviors that take priority
+ * over the default nested scrolling logic.
+ *
+ * When started, this connection intercepts scroll events *before* they reach child composables.
+ * This "priority mode" is activated activated when either [canStartPreScroll], [canStartPostScroll]
+ * or [canStartPostFling] returns `true`.
+ *
+ * Once started, the [onStart] lambda provides a [ScrollController] to manage the scrolling. This
+ * controller allows you to directly manipulate the scroll state and define how scroll events are
+ * consumed.
+ *
+ * **Important Considerations:**
+ * - When started, scroll events are typically consumed in `onPreScroll`.
+ * - The provided [ScrollController] should handle potential cancellation of `onStop` due to new
+ * gestures.
+ * - Use [reset] to release the current [ScrollController] and reset the connection to its initial
+ * state.
*
* @param orientation The orientation of the scroll.
- * @param canStartPreScroll lambda that returns true if the connection can start consuming scroll
- * events in pre-scroll mode.
- * @param canStartPostScroll lambda that returns true if the connection can start consuming scroll
- * events in post-scroll mode.
- * @param canStartPostFling lambda that returns true if the connection can start consuming scroll
- * events in post-fling mode.
- * @param canStopOnScroll lambda that returns true if the connection can stop consuming scroll
- * events in scroll mode.
- * @param canStopOnPreFling lambda that returns true if the connection can stop consuming scroll
- * events in pre-fling (i.e. as soon as the user lifts their fingers).
- * @param onStart lambda that is called when the connection starts consuming scroll events.
- * @param onScroll lambda that is called when the connection consumes a scroll event and returns the
- * consumed amount.
- * @param onStop lambda that is called when the connection stops consuming scroll events and returns
- * the consumed velocity.
- * @param onCancel lambda that is called when the connection is cancelled.
+ * @param canStartPreScroll A lambda that returns `true` if the connection should enter priority
+ * mode during the pre-scroll phase. This is called before child connections have a chance to
+ * consume the scroll.
+ * @param canStartPostScroll A lambda that returns `true` if the connection should enter priority
+ * mode during the post-scroll phase. This is called after child connections have consumed the
+ * scroll.
+ * @param canStartPostFling A lambda that returns `true` if the connection should enter priority
+ * mode during the post-fling phase. This is called after a fling gesture has been initiated.
+ * @param onStart A lambda that is called when the connection enters priority mode. It should return
+ * a [ScrollController] that will be used to control the scroll.
* @sample LargeTopAppBarNestedScrollConnection
* @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
*/
@@ -66,169 +126,213 @@ class PriorityNestedScrollConnection(
private val canStartPostScroll:
(offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
- private val canStopOnScroll: (available: Float, consumed: Float) -> Boolean = { _, consumed ->
- consumed == 0f
- },
- private val canStopOnPreFling: () -> Boolean,
- private val onStart: (offsetAvailable: Float) -> Unit,
- private val onScroll: (offsetAvailable: Float, source: NestedScrollSource) -> Float,
- private val onStop: suspend (velocityAvailable: Float) -> Float,
- private val onCancel: () -> Unit,
+ private val onStart: (firstScroll: Float) -> ScrollController,
) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
- /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
- private var isPriorityMode = false
+ /** The currently active [ScrollController], or `null` if not in priority mode. */
+ private var currentController: ScrollController? = null
+
+ /**
+ * A [Deferred] representing the ongoing `onStop` animation. Used to interrupt the animation if
+ * a new gesture occurs.
+ */
+ private var stoppingJob: Deferred<Float>? = null
+ /**
+ * Indicates whether the connection is currently in the process of stopping the scroll with the
+ * [ScrollController.onStop] animation.
+ */
+ private val isStopping
+ get() = stoppingJob?.isActive ?: false
+
+ /**
+ * Tracks the cumulative scroll offset that has been consumed by other composables before this
+ * connection enters priority mode. This is used to determine when the connection should take
+ * over scrolling based on the [canStartPreScroll] and [canStartPostScroll] conditions.
+ */
private var offsetScrolledBeforePriorityMode = 0f
- /** This job allows us to interrupt the onStop animation */
- private var onStopJob: Deferred<Float> = CompletableDeferred(0f)
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ // If stopping, interrupt the animation and clear the controller.
+ if (isStopping) {
+ interruptStopping()
+ }
+
+ // If in priority mode, consume the scroll using the current controller.
+ if (currentController != null) {
+ return scroll(available.toFloat(), source)
+ }
+
+ // Check if pre-scroll condition is met, and start priority mode if necessary.
+ val availableFloat = available.toFloat()
+ if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) {
+ start(availableFloat)
+ return scroll(availableFloat, source)
+ }
+
+ // Track offset consumed before entering priority mode.
+ offsetScrolledBeforePriorityMode += availableFloat
+ return Offset.Zero
+ }
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
+ // If in priority mode, scroll events are consumed only in pre-scroll phase.
+ if (currentController != null) return Offset.Zero
+
+ // Check if post-scroll condition is met, and start priority mode if necessary.
val availableFloat = available.toFloat()
- // The offset before the start takes into account the up and down movements, starting from
- // the beginning or from the last fling gesture.
val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
-
- if (isPriorityMode || !canStartPostScroll(availableFloat, offsetBeforeStart, source)) {
- // The priority mode cannot start so we won't consume the available offset.
- return Offset.Zero
+ if (canStartPostScroll(availableFloat, offsetBeforeStart, source)) {
+ start(availableFloat)
+ return scroll(availableFloat, source)
}
- return start(availableFloat, source).toOffset()
- }
-
- override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
- if (!isPriorityMode) {
- val availableFloat = available.toFloat()
- if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) {
- return start(availableFloat, source).toOffset()
- }
- // We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += availableFloat
- return Offset.Zero
- }
-
- return scroll(available.toFloat(), source).toOffset()
+ // Do not consume the offset if priority mode is not activated.
+ return Offset.Zero
}
override suspend fun onPreFling(available: Velocity): Velocity {
- if (!isPriorityMode) {
- resetOffsetTracker()
- return Velocity.Zero
- }
+ val controller = currentController ?: return Velocity.Zero
- if (canStopOnPreFling()) {
- // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the
- // velocity of the fling gesture.
- return stop(velocityAvailable = available.toFloat()).toVelocity()
+ // If in priority mode and can stop on pre-fling phase, stop the scroll.
+ if (controller.canStopOnPreFling()) {
+ return stop(velocity = available.toFloat())
}
- // We don't want to consume the velocity, we prefer to continue receiving scroll events.
+ // Do not consume the velocity if not stopping on pre-fling phase.
return Velocity.Zero
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
val availableFloat = available.toFloat()
- if (isPriorityMode) {
- return stop(velocityAvailable = availableFloat).toVelocity()
- }
+ val controller = currentController
- if (!canStartPostFling(availableFloat)) {
- return Velocity.Zero
+ // If in priority mode, stop the scroll.
+ if (controller != null) {
+ return stop(velocity = availableFloat)
}
- // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of 1px
- // given the available velocity.
+ // Check if post-fling condition is met, and start priority mode if necessary.
// TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
// overscroll behavior on the Scene level.
- val smallOffset = availableFloat.sign
- start(
- availableOffset = smallOffset,
- source = NestedScrollSource.SideEffect,
- skipScroll = true,
- )
-
- // This is the last event of a scroll gesture.
- return stop(availableFloat).toVelocity()
+ if (canStartPostFling(availableFloat)) {
+ // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of
+ // 1px given the available velocity.
+ val smallOffset = availableFloat.sign
+ start(availableOffset = smallOffset)
+ return stop(availableFloat)
+ }
+
+ // Reset offset tracking after the fling gesture is finished.
+ resetOffsetTracker()
+ return Velocity.Zero
}
/**
- * Method to call before destroying the object or to reset the initial state.
- *
- * TODO(b/303224944) This method should be removed.
+ * Resets the connection to its initial state. This cancels any ongoing scroll operation and
+ * clears the current [ScrollController].
*/
fun reset() {
- if (isPriorityMode) {
- // Step 3c: To ensure that an onStop (or onCancel) is always called for every onStart.
+ if (currentController != null && !isStopping) {
cancel()
} else {
resetOffsetTracker()
}
}
- private fun start(
- availableOffset: Float,
- source: NestedScrollSource,
- skipScroll: Boolean = false,
- ): Float {
- check(!isPriorityMode) {
- "This should never happen, start() was called when isPriorityMode"
- }
-
- // Step 1: It's our turn! We start capturing scroll events when one of our children has an
- // available offset following a scroll event.
- isPriorityMode = true
+ /**
+ * Starts priority mode by creating a new [ScrollController] using the [onStart] lambda.
+ *
+ * @param availableOffset The initial scroll offset available.
+ */
+ private fun start(availableOffset: Float) {
+ check(currentController == null) { "Another controller is active: $currentController" }
- onStopJob.cancel()
+ resetOffsetTracker()
- // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
- // lifted (step 3b), or this object has been destroyed (step 3c).
- onStart(availableOffset)
+ currentController = onStart(availableOffset)
+ }
- return if (skipScroll) 0f else scroll(availableOffset, source)
+ /**
+ * Retrieves the current [ScrollController], ensuring that it is not null and that the
+ * [isStopping] state matches the expected value.
+ */
+ private fun requireController(isStopping: Boolean): ScrollController {
+ check(this.isStopping == isStopping) {
+ "isStopping is ${this.isStopping}, instead of $isStopping"
+ }
+ check(offsetScrolledBeforePriorityMode == 0f) {
+ "offset scrolled should be zero, but it was $offsetScrolledBeforePriorityMode"
+ }
+ return checkNotNull(currentController) { "The controller is $currentController" }
}
- private fun scroll(offsetAvailable: Float, source: NestedScrollSource): Float {
- // Step 2: We have the priority and can consume the scroll events.
- val consumedByScroll = onScroll(offsetAvailable, source)
+ /**
+ * Scrolls the content using the current [ScrollController].
+ *
+ * @param delta The amount of scroll to apply.
+ * @param source The source of the scroll event.
+ * @return The amount of scroll consumed.
+ */
+ private fun scroll(delta: Float, source: NestedScrollSource): Offset {
+ val controller = requireController(isStopping = false)
+ val consumedByScroll = controller.onScroll(delta, source)
- if (canStopOnScroll(offsetAvailable, consumedByScroll)) {
- // Step 3a: We have lost priority and we no longer need to intercept scroll events.
+ if (controller.canCancelScroll(delta, consumedByScroll)) {
+ // We have lost priority and we no longer need to intercept scroll events.
cancel()
-
- // We've just reset offsetScrolledBeforePriorityMode to 0f
- // We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += offsetAvailable - consumedByScroll
+ offsetScrolledBeforePriorityMode = delta - consumedByScroll
}
- return consumedByScroll
+ return consumedByScroll.toOffset()
}
- /** Reset the tracking of consumed offsets before entering in priority mode. */
- private fun resetOffsetTracker() {
- offsetScrolledBeforePriorityMode = 0f
+ /** Cancels the current scroll operation and clears the current [ScrollController]. */
+ private fun cancel() {
+ requireController(isStopping = false).onCancel()
+ currentController = null
}
- private suspend fun stop(velocityAvailable: Float): Float {
- check(isPriorityMode) { "This should never happen, stop() was called before start()" }
- isPriorityMode = false
- resetOffsetTracker()
-
+ /**
+ * Stops the scroll with the given velocity using the current [ScrollController].
+ *
+ * @param velocity The velocity to stop with.
+ * @return The consumed velocity.
+ */
+ suspend fun stop(velocity: Float): Velocity {
+ val controller = requireController(isStopping = false)
return coroutineScope {
- onStopJob = async { onStop(velocityAvailable) }
- onStopJob.await()
+ try {
+ async { controller.onStop(velocity) }
+ // Allows others to interrupt the job.
+ .also { stoppingJob = it }
+ // Note: this can be cancelled by [interruptStopping]
+ .await()
+ .toVelocity()
+ } finally {
+ // If the job is interrupted, it might take a while to cancel. We need to make sure
+ // the current controller is still the initial one.
+ if (currentController == controller) {
+ currentController = null
+ }
+ }
}
}
- private fun cancel() {
- check(isPriorityMode) { "This should never happen, cancel() was called before start()" }
- isPriorityMode = false
- resetOffsetTracker()
- onCancel()
+ /** Interrupts the ongoing stop animation and clears the current [ScrollController]. */
+ private fun interruptStopping() {
+ requireController(isStopping = true)
+ // We are throwing a CancellationException in the [ScrollController.onStop] method.
+ stoppingJob?.cancel()
+ currentController = null
+ }
+
+ /** Resets the tracking of consumed offsets before entering priority mode. */
+ private fun resetOffsetTracker() {
+ offsetScrolledBeforePriorityMode = 0f
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 5edb99ea0795..37dae39f935d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -225,6 +225,40 @@ class NestedScrollToSceneTest {
}
@Test
+ fun stlNotConsumeUnobservedGesture() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(SceneA, transitions = EmptyTestTransitions)
+ }
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(
+ state = state,
+ modifier = Modifier.size(layoutWidth, layoutHeight),
+ ) {
+ scene(SceneA) {
+ Spacer(
+ Modifier.verticalNestedScrollToScene()
+ // This scrollable will not consume the gesture.
+ .scrollable(rememberScrollableState { 0f }, Vertical)
+ .fillMaxSize()
+ )
+ }
+ }
+ }
+
+ rule.onRoot().performTouchInput {
+ down(Offset.Zero)
+ // There is no vertical scene.
+ moveBy(Offset(0f, layoutWidth.toPx()), delayMillis = 1_000)
+ }
+
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ }
+
+ @Test
fun customizeStlNestedScrollBehavior_multipleRequests() {
var canScroll = true
val state = setup2ScenesAndScrollTouchSlop {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 1a3b86b936df..0364cdc4166e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -20,6 +20,7 @@ package com.android.compose.nestedscroll
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -48,17 +49,26 @@ class PriorityNestedScrollConnectionTest {
canStartPreScroll = { _, _, _ -> canStartPreScroll },
canStartPostScroll = { _, _, _ -> canStartPostScroll },
canStartPostFling = { canStartPostFling },
- canStopOnPreFling = { canStopOnPreFling },
- onStart = { isStarted = true },
- onScroll = { offsetAvailable, _ ->
- lastScroll = offsetAvailable
- if (consumeScroll) offsetAvailable else 0f
+ onStart = { _ ->
+ isStarted = true
+ object : ScrollController {
+ override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+ lastScroll = deltaScroll
+ return if (consumeScroll) deltaScroll else 0f
+ }
+
+ override suspend fun onStop(initialVelocity: Float): Float {
+ lastStop = initialVelocity
+ return if (consumeStop) initialVelocity else 0f
+ }
+
+ override fun onCancel() {
+ isCancelled = true
+ }
+
+ override fun canStopOnPreFling() = canStopOnPreFling
+ }
},
- onStop = {
- lastStop = it
- if (consumeStop) it else 0f
- },
- onCancel = { isCancelled = true },
)
@Test
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
index 2f1d354c3b3e..afdcae481df5 100644
--- a/packages/SystemUI/log/Android.bp
+++ b/packages/SystemUI/log/Android.bp
@@ -22,19 +22,15 @@ package {
default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
}
-android_library {
+java_library {
name: "SystemUILogLib",
- use_resource_processor: true,
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
static_libs: [
- "androidx.core_core-ktx",
- "androidx.annotation_annotation",
"error_prone_annotations",
"SystemUICommon",
],
- manifest: "AndroidManifest.xml",
kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SystemUI/log/AndroidManifest.xml b/packages/SystemUI/log/AndroidManifest.xml
deleted file mode 100644
index 4021e1a5f751..000000000000
--- a/packages/SystemUI/log/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.log">
-
-
-</manifest>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index bd33e52689c2..58c3fec5b45e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,12 +29,11 @@ import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.VelocityTracker;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -45,12 +42,9 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
-import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -64,14 +58,10 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.List;
import java.util.Optional;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
@@ -124,11 +114,6 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
@Mock
KeyguardInteractor mKeyguardInteractor;
- @Mock
- WindowRootView mWindowRootView;
-
- private SceneInteractor mSceneInteractor;
-
private static final float TOUCH_REGION = .3f;
private static final float MIN_BOUNCER_HEIGHT = .05f;
@@ -139,21 +124,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
/* flags= */ 0
);
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getParams() {
- return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
- }
-
- public BouncerFullscreenSwipeTouchHandlerTest(FlagsParameterization flags) {
- super();
- mSetFlagsRule.setFlagsParameterization(flags);
- }
-
@Before
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
- mSceneInteractor = spy(mKosmos.getSceneInteractor());
-
MockitoAnnotations.initMocks(this);
mTouchHandler = new BouncerSwipeTouchHandler(
mKosmos.getTestScope(),
@@ -169,9 +142,7 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
MIN_BOUNCER_HEIGHT,
mUiEventLogger,
mActivityStarter,
- mKeyguardInteractor,
- mSceneInteractor,
- Optional.of(() -> mWindowRootView));
+ mKeyguardInteractor);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -182,38 +153,6 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
}
/**
- * Makes sure that touches go to the scene container when the flag is on.
- */
- @Test
- @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
- public void testSwipeUp_sendsTouchesToWindowRootView() {
- mTouchHandler.onGlanceableTouchAvailable(true);
- mTouchHandler.onSessionStart(mTouchSession);
- ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
- ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
- verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
-
- final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
-
- final int screenHeight = 100;
- final float distanceY = screenHeight * 0.42f;
-
- final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
- 0, screenHeight, 0);
- final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
- 0, screenHeight - distanceY, 0);
-
- assertThat(gestureListener.onScroll(event1, event2, 0,
- distanceY))
- .isTrue();
-
- // Ensure only called once
- verify(mSceneInteractor).onRemoteUserInputStarted(any());
- verify(mWindowRootView).dispatchTouchEvent(event1);
- verify(mWindowRootView).dispatchTouchEvent(event2);
- }
-
- /**
* Ensures expansion does not happen for full vertical swipes when touch is not available.
*/
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 494e0b4deef4..95681671b545 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -38,12 +37,12 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.VelocityTracker;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -53,12 +52,9 @@ import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
-import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -74,14 +70,10 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.List;
import java.util.Optional;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
private KosmosJavaAdapter mKosmos;
@@ -130,9 +122,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
Region mRegion;
@Mock
- WindowRootView mWindowRootView;
-
- @Mock
CommunalViewModel mCommunalViewModel;
@Mock
@@ -141,8 +130,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@Captor
ArgumentCaptor<Rect> mRectCaptor;
- private SceneInteractor mSceneInteractor;
-
private static final float TOUCH_REGION = .3f;
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
@@ -155,21 +142,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
/* flags= */ 0
);
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getParams() {
- return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
- }
-
- public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) {
- super();
- mSetFlagsRule.setFlagsParameterization(flags);
- }
-
@Before
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
- mSceneInteractor = spy(mKosmos.getSceneInteractor());
-
MockitoAnnotations.initMocks(this);
mTouchHandler = new BouncerSwipeTouchHandler(
mKosmos.getTestScope(),
@@ -185,10 +160,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
MIN_BOUNCER_HEIGHT,
mUiEventLogger,
mActivityStarter,
- mKeyguardInteractor,
- mSceneInteractor,
- Optional.of(() -> mWindowRootView)
- );
+ mKeyguardInteractor);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -395,7 +367,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
* Makes sure the expansion amount is proportional to (1 - scroll).
*/
@Test
- @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
public void testSwipeUp_setsCorrectExpansionAmount() {
mTouchHandler.onSessionStart(mTouchSession);
ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
@@ -409,36 +380,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
}
/**
- * Makes sure that touches go to the scene container when the flag is on.
- */
- @Test
- @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
- public void testSwipeUp_sendsTouchesToWindowRootView() {
- mTouchHandler.onSessionStart(mTouchSession);
- ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
- ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
- verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
-
- final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
-
- final float distanceY = SCREEN_HEIGHT_PX * 0.42f;
-
- final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
- 0, SCREEN_HEIGHT_PX, 0);
- final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
- 0, SCREEN_HEIGHT_PX - distanceY, 0);
-
- assertThat(gestureListener.onScroll(event1, event2, 0,
- distanceY))
- .isTrue();
-
- // Ensure only called once
- verify(mSceneInteractor).onRemoteUserInputStarted(any());
- verify(mWindowRootView).dispatchTouchEvent(event1);
- verify(mWindowRootView).dispatchTouchEvent(event2);
- }
-
- /**
* Verifies that swiping up when the lock pattern is not secure dismissed dream and consumes
* the gesture.
*/
@@ -535,7 +476,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
* Tests that ending an upward swipe before the set threshold leads to bouncer collapsing down.
*/
@Test
- @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
public void testSwipeUpPositionBelowThreshold_collapsesBouncer() {
final float swipeUpPercentage = .3f;
final float expansion = 1 - swipeUpPercentage;
@@ -559,7 +499,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
* Tests that ending an upward swipe above the set threshold will continue the expansion.
*/
@Test
- @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
public void testSwipeUpPositionAboveThreshold_expandsBouncer() {
final float swipeUpPercentage = .7f;
final float expansion = 1 - swipeUpPercentage;
@@ -589,7 +528,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
* Tests that swiping up with a speed above the set threshold will continue the expansion.
*/
@Test
- @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
public void testSwipeUpVelocityAboveMin_expandsBouncer() {
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index ad636cf613ad..38ea44976175 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -18,9 +18,9 @@ package com.android.systemui.ambient.touch
import android.app.DreamManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.FlagsParameterization
import android.view.GestureDetector
import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -28,20 +28,14 @@ import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
-import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.testKosmos
import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
import java.util.Optional
-import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,29 +47,22 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class ShadeTouchHandlerTest : SysuiTestCase() {
private var kosmos = testKosmos()
private var mCentralSurfaces = mock<CentralSurfaces>()
private var mShadeViewController = mock<ShadeViewController>()
private var mDreamManager = mock<DreamManager>()
private var mTouchSession = mock<TouchSession>()
private var communalViewModel = mock<CommunalViewModel>()
- private var windowRootView = mock<WindowRootView>()
private lateinit var mTouchHandler: ShadeTouchHandler
private var mGestureListenerCaptor = argumentCaptor<GestureDetector.OnGestureListener>()
private var mInputListenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>()
- init {
- mSetFlagsRule.setFlagsParameterization(flags)
- }
-
@Before
fun setup() {
mTouchHandler =
@@ -86,9 +73,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
mDreamManager,
communalViewModel,
kosmos.communalSettingsInteractor,
- kosmos.sceneInteractor,
- Optional.of(Provider<WindowRootView> { windowRootView }),
- TOUCH_HEIGHT,
+ TOUCH_HEIGHT
)
}
@@ -112,7 +97,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
// Verifies that a swipe down forwards captured touches to central surfaces for handling.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -125,11 +110,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
// Verifies that a swipe down forwards captured touches to the shade view for handling.
@Test
- @DisableFlags(
- Flags.FLAG_COMMUNAL_HUB,
- Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX,
- Flags.FLAG_SCENE_CONTAINER,
- )
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeDown_communalDisabled_sentToShadeView() {
swipe(Direction.DOWN)
@@ -140,7 +121,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
// Verifies that a swipe down while dreaming forwards captured touches to the shade view for
// handling.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeDown_dreaming_sentToShadeView() {
whenever(mDreamManager.isDreaming).thenReturn(true)
swipe(Direction.DOWN)
@@ -149,34 +130,9 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
verify(mShadeViewController, times(2)).handleExternalTouch(any())
}
- // Verifies that a swipe down forwards captured touches to the window root view for handling.
- @Test
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_SCENE_CONTAINER)
- fun testSwipeDown_sceneContainerEnabled_sentToWindowRootView() {
- swipe(Direction.DOWN)
-
- // Both motion events are sent for central surfaces to process.
- assertThat(kosmos.sceneContainerRepository.isRemoteUserInputOngoing.value).isTrue()
- verify(windowRootView, times(2)).dispatchTouchEvent(any())
- }
-
- // Verifies that a swipe down while dreaming forwards captured touches to the window root view
- // for handling.
- @Test
- @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
- fun testSwipeDown_dreaming_sentToWindowRootView() {
- whenever(mDreamManager.isDreaming).thenReturn(true)
- swipe(Direction.DOWN)
-
- // Both motion events are sent for the shade view to process.
- assertThat(kosmos.sceneContainerRepository.isRemoteUserInputOngoing.value).isTrue()
- verify(windowRootView, times(2)).dispatchTouchEvent(any())
- }
-
// Verifies that a swipe up is not forwarded to central surfaces.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeUp_communalEnabled_touchesNotSent() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -190,11 +146,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
// Verifies that a swipe up is not forwarded to the shade view.
@Test
- @DisableFlags(
- Flags.FLAG_COMMUNAL_HUB,
- Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX,
- Flags.FLAG_SCENE_CONTAINER,
- )
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeUp_communalDisabled_touchesNotSent() {
swipe(Direction.UP)
@@ -203,17 +155,6 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
verify(mShadeViewController, never()).handleExternalTouch(any())
}
- // Verifies that a swipe up is not forwarded to the window root view.
- @Test
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_SCENE_CONTAINER)
- fun testSwipeUp_sceneContainerEnabled_touchesNotSent() {
- swipe(Direction.UP)
-
- // Motion events are not sent for window root view to process as the swipe is going in the
- // wrong direction.
- verify(windowRootView, never()).dispatchTouchEvent(any())
- }
-
@Test
@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testCancelMotionEvent_popsTouchSession() {
@@ -302,16 +243,10 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
private enum class Direction {
DOWN,
- UP,
+ UP
}
companion object {
private const val TOUCH_HEIGHT = 20
-
- @JvmStatic
- @Parameters(name = "{0}")
- fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
- }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 7d4918a30d9c..b5043ce700f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -39,7 +39,7 @@ class ShadeRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
- underTest = ShadeRepositoryImpl(getContext())
+ underTest = ShadeRepositoryImpl()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
index 58943ea3b4ee..c8ae3586221d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -23,6 +25,7 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.app.Notification;
+import android.app.NotificationChannelGroup;
import android.app.RemoteInputHistoryItem;
import android.net.Uri;
import android.os.UserHandle;
@@ -71,6 +74,25 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
}
@Test
+ public void testRebuildWithRemoteInput_invalidData() {
+ Uri uri = mock(Uri.class);
+ String mimeType = "image/jpeg";
+ String text = "image inserted";
+ mEntry.getSbn().getNotification().extras.putParcelableArray(
+ EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ new NotificationChannelGroup[]{});
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildWithRemoteInputInserted(
+ mEntry, text, false, mimeType, uri);
+ RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+ .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ assertEquals(1, messages.length);
+ assertEquals(text, messages[0].getText());
+ assertEquals(mimeType, messages[0].getMimeType());
+ assertEquals(uri, messages[0].getUri());
+ }
+
+ @Test
public void testRebuildWithRemoteInput_noExistingInput_image() {
Uri uri = mock(Uri.class);
String mimeType = "image/jpeg";
@@ -79,7 +101,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
mRebuilder.rebuildWithRemoteInputInserted(
mEntry, text, false, mimeType, uri);
RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
assertEquals(1, messages.length);
assertEquals(text, messages[0].getText());
assertEquals(mimeType, messages[0].getMimeType());
@@ -92,7 +114,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
mRebuilder.rebuildWithRemoteInputInserted(
mEntry, "A Reply", false, null, null);
RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
assertEquals(1, messages.length);
assertEquals("A Reply", messages[0].getText());
assertFalse(newSbn.getNotification().extras
@@ -107,7 +129,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
mRebuilder.rebuildWithRemoteInputInserted(
mEntry, "A Reply", true, null, null);
RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
assertEquals(1, messages.length);
assertEquals("A Reply", messages[0].getText());
assertTrue(newSbn.getNotification().extras
@@ -130,7 +152,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
newSbn = mRebuilder.rebuildWithRemoteInputInserted(
entry, "Reply 2", true, null, null);
RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
assertEquals(2, messages.length);
assertEquals("Reply 2", messages[0].getText());
assertEquals("A Reply", messages[1].getText());
@@ -153,7 +175,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
newSbn = mRebuilder.rebuildWithRemoteInputInserted(
entry, "Reply 2", true, null, null);
RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
assertEquals(2, messages.length);
assertEquals("Reply 2", messages[0].getText());
assertEquals(text, messages[1].getText());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 25670cb0bb0c..33bf3507adb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -637,6 +637,45 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
@Test
@DisableSceneContainer
+ fun boundsStableWhenGoingToAlternateBouncer() =
+ testScope.runTest {
+ val bounds by collectLastValue(underTest.bounds)
+
+ // Start on lockscreen
+ showLockscreen()
+
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 1f, bottom = 2f)
+ )
+
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
+
+ // Begin transition to AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 0f, TransitionState.STARTED)
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 0f, TransitionState.RUNNING)
+ )
+ runCurrent()
+
+ // This is the last step before FINISHED is sent, which could trigger a change in bounds
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 1f, TransitionState.RUNNING)
+ )
+ runCurrent()
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 1f, TransitionState.FINISHED)
+ )
+ runCurrent()
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
+ }
+
+ @Test
+ @DisableSceneContainer
fun boundsDoNotChangeWhileLockscreenToAodTransitionIsActive() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
@@ -1229,6 +1268,75 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
assertThat(alpha).isEqualTo(1f)
}
+ @Test
+ @DisableSceneContainer
+ fun notificationAbsoluteBottom() =
+ testScope.runTest {
+ var notificationCount = 2
+ val calculateSpace = { _: Float, _: Boolean -> notificationCount }
+ val shelfHeight = 10F
+ val heightForNotification = 20F
+ val calculateHeight = { count: Int -> count * heightForNotification + shelfHeight }
+ val stackAbsoluteBottom by
+ collectLastValue(
+ underTest.getNotificationStackAbsoluteBottom(
+ calculateSpace,
+ calculateHeight,
+ shelfHeight,
+ )
+ )
+ advanceTimeBy(50L)
+ showLockscreen()
+
+ shadeTestUtil.setSplitShade(false)
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 100F, bottom = 300F)
+ )
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(stackAbsoluteBottom).isEqualTo(150F)
+
+ // Also updates when directly requested (as it would from NotificationStackScrollLayout)
+ notificationCount = 3
+ sharedNotificationContainerInteractor.notificationStackChanged()
+ advanceTimeBy(50L)
+ assertThat(stackAbsoluteBottom).isEqualTo(170F)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun notificationAbsoluteBottom_maxNotificationIsZero_noShelfHeight() =
+ testScope.runTest {
+ var notificationCount = 2
+ val calculateSpace = { _: Float, _: Boolean -> notificationCount }
+ val shelfHeight = 10F
+ val heightForNotification = 20F
+ val calculateHeight = { count: Int -> count * heightForNotification + shelfHeight }
+ val stackAbsoluteBottom by
+ collectLastValue(
+ underTest.getNotificationStackAbsoluteBottom(
+ calculateSpace,
+ calculateHeight,
+ shelfHeight,
+ )
+ )
+ advanceTimeBy(50L)
+ showLockscreen()
+
+ shadeTestUtil.setSplitShade(false)
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 100F, bottom = 300F)
+ )
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(stackAbsoluteBottom).isEqualTo(150F)
+
+ notificationCount = 0
+ sharedNotificationContainerInteractor.notificationStackChanged()
+ advanceTimeBy(50L)
+ assertThat(stackAbsoluteBottom).isEqualTo(100F)
+ }
+
private suspend fun TestScope.showLockscreen() {
shadeTestUtil.setQsExpansion(0f)
shadeTestUtil.setLockscreenShadeExpansion(0f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 59676ce126da..111c232280c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -25,9 +25,4 @@ class FakeWallpaperRepository : WallpaperRepository {
override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
override var rootView: View? = null
- private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
-
- override fun setNotificationStackAbsoluteBottom(bottom: Float) {
- _notificationStackAbsoluteBottom.value = bottom
- }
}
diff --git a/packages/SystemUI/plugin_core/AndroidManifest.xml b/packages/SystemUI/plugin_core/AndroidManifest.xml
deleted file mode 100644
index df835fd8e32d..000000000000
--- a/packages/SystemUI/plugin_core/AndroidManifest.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.plugin_core">
-
- <uses-sdk
- android:minSdkVersion="28" />
-
-</manifest>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_history.xml b/packages/SystemUI/res/drawable/notif_footer_btn_history.xml
new file mode 100644
index 000000000000..0460a7268fcc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_history.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,840Q342,840 239.5,748.5Q137,657 122,520L204,520Q218,624 296.5,692Q375,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L360,320L360,400L120,400L120,160L200,160L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM592,648L440,496L440,280L520,280L520,464L648,592L592,648Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml b/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
new file mode 100644
index 000000000000..800060db7757
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml b/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
new file mode 100644
index 000000000000..7c59aad10c91
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
@@ -0,0 +1,82 @@
+<!--
+ ~ Copyright (C) 2024 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
+ -->
+
+<!-- Extends Framelayout -->
+<com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="12dp">
+
+ <TextView
+ android:id="@+id/unlock_prompt_footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:drawablePadding="8dp"
+ android:gravity="center"
+ android:text="@string/unlock_to_see_notif_text"
+ android:textAppearance="?android:attr/textAppearanceButton"
+ android:visibility="gone" />
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ android:id="@+id/settings_button"
+ style="@style/TextAppearance.NotificationFooterButtonRedesign"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:background="@drawable/notif_footer_btn_background"
+ android:contentDescription="@string/notification_settings_button_description"
+ android:drawableStart="@drawable/notif_footer_btn_settings"
+ android:focusable="true"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ android:id="@+id/dismiss_text"
+ style="@style/TextAppearance.NotificationFooterButtonRedesign"
+ android:layout_width="0dp"
+ android:layout_height="48dp"
+ android:layout_marginHorizontal="8dp"
+ android:background="@drawable/notif_footer_btn_background"
+ android:contentDescription="@string/accessibility_clear_all"
+ android:focusable="true"
+ android:text="@string/clear_all_notifications_text"
+ app:layout_constraintEnd_toStartOf="@id/history_button"
+ app:layout_constraintStart_toEndOf="@id/settings_button" />
+
+ <com.android.systemui.statusbar.notification.row.FooterViewButton
+ android:id="@+id/history_button"
+ style="@style/TextAppearance.NotificationFooterButtonRedesign"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:background="@drawable/notif_footer_btn_background"
+ android:contentDescription="@string/notification_history_button_description"
+ android:drawableStart="@drawable/notif_footer_btn_history"
+ android:focusable="true"
+ app:layout_constraintEnd_toEndOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+</com.android.systemui.statusbar.notification.footer.ui.view.FooterView>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c494e8525e0f..0aa5ccf7a2b4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1463,6 +1463,12 @@
<!-- The text for the notification history link. [CHAR LIMIT=40] -->
<string name="manage_notifications_history_text">History</string>
+ <!-- The accessibility description for the notification settings button in the notification shade. -->
+ <string name="notification_settings_button_description">Notification settings</string>
+
+ <!-- The accessibility description for the notification history button in the notification shade. -->
+ <string name="notification_history_button_description">Notification history</string>
+
<!-- Section title for notifications that have recently appeared. [CHAR LIMIT=40] -->
<string name="notification_section_header_incoming">New</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1ab9242438b9..ab45b9f1b5bc 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -785,6 +785,16 @@
<item name="android:minWidth">0dp</item>
</style>
+ <style
+ name="TextAppearance.NotificationFooterButtonRedesign"
+ parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:drawableTint">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:minWidth">0dp</item>
+ </style>
+
<style name="TextAppearance.HeadsUpStatusBarText"
parent="@*android:style/TextAppearance.DeviceDefault.Notification.Info">
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index 7b3d337306b4..223a21db2016 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -38,9 +38,6 @@ import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -48,7 +45,6 @@ import com.android.wm.shell.animation.FlingAnimationUtils
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
-import javax.inject.Provider
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
@@ -78,8 +74,6 @@ constructor(
private val uiEventLogger: UiEventLogger,
private val activityStarter: ActivityStarter,
private val keyguardInteractor: KeyguardInteractor,
- private val sceneInteractor: SceneInteractor,
- private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
) : TouchHandler {
/** An interface for creating ValueAnimators. */
interface ValueAnimatorCreator {
@@ -106,8 +100,6 @@ constructor(
currentScrimController = controller
}
- private val windowRootView by lazy { windowRootViewProvider.get().get() }
-
/** Determines whether the touch handler should process touches in fullscreen swiping mode */
private var touchAvailable = false
@@ -117,7 +109,7 @@ constructor(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
- distanceY: Float,
+ distanceY: Float
): Boolean {
if (capture == null) {
capture =
@@ -136,11 +128,6 @@ constructor(
expanded = false
// Since the user is dragging the bouncer up, set scrimmed to false.
currentScrimController?.show()
-
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.onRemoteUserInputStarted("bouncer touch handler")
- e1?.apply { windowRootView.dispatchTouchEvent(e1) }
- }
}
}
if (capture != true) {
@@ -165,27 +152,20 @@ constructor(
/* cancelAction= */ null,
/* dismissShade= */ true,
/* afterKeyguardGone= */ true,
- /* deferred= */ false,
+ /* deferred= */ false
)
return true
}
- if (SceneContainerFlag.isEnabled) {
- windowRootView.dispatchTouchEvent(e2)
- } else {
- // For consistency, we adopt the expansion definition found in the
- // PanelViewController. In this case, expansion refers to the view above the
- // bouncer. As that view's expansion shrinks, the bouncer appears. The
- // bouncer
- // is fully hidden at full expansion (1) and fully visible when fully
- // collapsed
- // (0).
- touchSession?.apply {
- val screenTravelPercentage =
- (abs((this@outer.y - e2.y).toDouble()) / getBounds().height())
- .toFloat()
- setPanelExpansion(1 - screenTravelPercentage)
- }
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+ // is fully hidden at full expansion (1) and fully visible when fully collapsed
+ // (0).
+ touchSession?.apply {
+ val screenTravelPercentage =
+ (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat()
+ setPanelExpansion(1 - screenTravelPercentage)
}
}
@@ -214,7 +194,7 @@ constructor(
ShadeExpansionChangeEvent(
/* fraction= */ currentExpansion,
/* expanded= */ expanded,
- /* tracking= */ true,
+ /* tracking= */ true
)
currentScrimController?.expand(event)
}
@@ -367,7 +347,7 @@ constructor(
currentHeight,
targetHeight,
velocity,
- viewHeight,
+ viewHeight
)
} else {
// Shows the bouncer, i.e., fully collapses the space above the bouncer.
@@ -376,7 +356,7 @@ constructor(
currentHeight,
targetHeight,
velocity,
- viewHeight,
+ viewHeight
)
}
animator.start()
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
index 05a10092695b..1951a23ebda7 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -27,15 +27,11 @@ import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.ambient.touch.dagger.ShadeModule
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.phone.CentralSurfaces
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
-import javax.inject.Provider
import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
import com.android.app.tracing.coroutines.launchTraced as launch
@@ -53,10 +49,8 @@ constructor(
private val dreamManager: DreamManager,
private val communalViewModel: CommunalViewModel,
private val communalSettingsInteractor: CommunalSettingsInteractor,
- private val sceneInteractor: SceneInteractor,
- private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
@param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
- private val initiationHeight: Int,
+ private val initiationHeight: Int
) : TouchHandler {
/**
* Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
@@ -66,8 +60,6 @@ constructor(
/** Determines whether the touch handler should process touches in fullscreen swiping mode */
private var touchAvailable = false
- private val windowRootView by lazy { windowRootViewProvider.get().get() }
-
init {
if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
scope.launch {
@@ -108,7 +100,7 @@ constructor(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
- distanceY: Float,
+ distanceY: Float
): Boolean {
if (capture == null) {
// Only capture swipes that are going downwards.
@@ -118,10 +110,6 @@ constructor(
if (Flags.hubmodeFullscreenVerticalSwipeFix()) touchAvailable
else true
if (capture == true) {
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.onRemoteUserInputStarted("shade touch handler")
- }
-
// Send the initial touches over, as the input listener has already
// processed these touches.
e1?.apply { sendTouchEvent(this) }
@@ -135,7 +123,7 @@ constructor(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
- velocityY: Float,
+ velocityY: Float
): Boolean {
return capture == true
}
@@ -144,11 +132,6 @@ constructor(
}
private fun sendTouchEvent(event: MotionEvent) {
- if (SceneContainerFlag.isEnabled) {
- windowRootView.dispatchTouchEvent(event)
- return
- }
-
if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
// Send touches to central surfaces only when on the glanceable hub while not dreaming.
// While sending touches where while dreaming will open the shade, the shade
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
index 1c781d608b04..bc2f35467312 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
@@ -22,10 +22,8 @@ import com.android.systemui.ambient.touch.ShadeTouchHandler;
import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
-import com.android.systemui.scene.ui.view.WindowRootView;
import dagger.Binds;
-import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
@@ -53,13 +51,6 @@ public abstract class ShadeModule {
ShadeTouchHandler touchHandler);
/**
- * Window root view is used to send touches to the scene container. Declaring as optional as it
- * may not be present on all SysUI variants.
- */
- @BindsOptionalOf
- abstract WindowRootView bindWindowRootView();
-
- /**
* Provides the height of the gesture area for notification swipe down.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 7b8c19c67d9b..ec5540172be5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -73,7 +73,7 @@ constructor(
private var progressJob: Job? = null
private val currentToState: KeyguardState
- get() = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+ get() = internalTransitionInteractor.currentTransitionInfoInternal().to
/**
* The next keyguard state to trigger when exiting [CommunalScenes.Communal]. This is only used
@@ -197,7 +197,7 @@ constructor(
val newTransition =
TransitionInfo(
ownerName = this::class.java.simpleName,
- from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
+ from = internalTransitionInteractor.currentTransitionInfoInternal().to,
to = state,
animator = null,
modeOnCanceled = TransitionModeOnCanceled.REVERSE,
@@ -273,7 +273,7 @@ constructor(
}
private suspend fun startTransitionToGlanceableHub() {
- val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+ val currentState = internalTransitionInteractor.currentTransitionInfoInternal().to
val newTransition =
TransitionInfo(
ownerName = this::class.java.simpleName,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1ffbbd2a9f32..7a6ca0859a09 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -65,6 +65,7 @@ import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -502,10 +503,10 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mDreamOverlayContainerViewController =
dreamOverlayComponent.getDreamOverlayContainerViewController();
- // Touch monitor are also used with SceneContainer. See individual touch handlers for
- // handling of SceneContainer.
- mTouchMonitor = ambientTouchComponent.getTouchMonitor();
- mTouchMonitor.init();
+ if (!SceneContainerFlag.isEnabled()) {
+ mTouchMonitor = ambientTouchComponent.getTouchMonitor();
+ mTouchMonitor.init();
+ }
mStateController.setShouldShowComplications(shouldShowComplications());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 85fb90d32d98..12984efb52c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -30,10 +30,8 @@ import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.res.R;
-import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.touch.TouchInsetManager;
-import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
@@ -56,13 +54,6 @@ public abstract class DreamOverlayModule {
public static final String DREAM_IN_TRANSLATION_Y_DURATION =
"dream_in_complications_translation_y_duration";
- /**
- * Window root view is used to send touches to the scene container. Declaring as optional as it
- * may not be present on all SysUI variants.
- */
- @BindsOptionalOf
- abstract WindowRootView bindWindowRootView();
-
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 42a68771cdcf..5ba780f9c99d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -31,9 +31,6 @@ import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import kotlinx.coroutines.Job;
@@ -45,7 +42,6 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
-import javax.inject.Provider;
/** {@link TouchHandler} responsible for handling touches to open communal hub. **/
public class CommunalTouchHandler implements TouchHandler {
@@ -55,8 +51,6 @@ public class CommunalTouchHandler implements TouchHandler {
private final CommunalInteractor mCommunalInteractor;
private final ConfigurationInteractor mConfigurationInteractor;
- private final SceneInteractor mSceneInteractor;
- private final WindowRootView mWindowRootView;
private Boolean mIsEnabled = false;
private ArrayList<Job> mFlows = new ArrayList<>();
@@ -75,16 +69,12 @@ public class CommunalTouchHandler implements TouchHandler {
@Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
CommunalInteractor communalInteractor,
ConfigurationInteractor configurationInteractor,
- SceneInteractor sceneInteractor,
- Optional<Provider<WindowRootView>> windowRootViewProvider,
Lifecycle lifecycle) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
mLifecycle = lifecycle;
mCommunalInteractor = communalInteractor;
mConfigurationInteractor = configurationInteractor;
- mSceneInteractor = sceneInteractor;
- mWindowRootView = windowRootViewProvider.get().get();
mFlows.add(collectFlow(
mLifecycle,
@@ -135,15 +125,8 @@ public class CommunalTouchHandler implements TouchHandler {
private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
// Notification shade window has its own logic to be visible if the hub is open, no need to
// do anything here other than send touch events over.
- if (SceneContainerFlag.isEnabled()) {
- mSceneInteractor.onRemoteUserInputStarted("communal touch handler");
- }
session.registerInputListener(ev -> {
- if (SceneContainerFlag.isEnabled()) {
- mWindowRootView.dispatchTouchEvent((MotionEvent) ev);
- } else {
- surfaces.handleCommunalHubTouch((MotionEvent) ev);
- }
+ surfaces.handleCommunalHubTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
var unused = session.pop();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index bbaa9eb85d4a..d3c17ccd2d18 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -269,6 +269,8 @@ interface KeyguardRepository {
/** The top of shortcut in screen, used by wallpaper to find remaining space in lockscreen */
val shortcutAbsoluteTop: StateFlow<Float>
+ val notificationStackAbsoluteBottom: StateFlow<Float>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -339,6 +341,12 @@ interface KeyguardRepository {
fun isShowKeyguardWhenReenabled(): Boolean
fun setShortcutAbsoluteTop(top: Float)
+
+ /**
+ * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
+ * this value
+ */
+ fun setNotificationStackAbsoluteBottom(bottom: Float)
}
/** Encapsulates application state for the keyguard. */
@@ -635,6 +643,9 @@ constructor(
private val _shortcutAbsoluteTop = MutableStateFlow(0F)
override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
+ private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
+ override val notificationStackAbsoluteBottom = _notificationStackAbsoluteBottom.asStateFlow()
+
init {
val callback =
object : KeyguardStateController.Callback {
@@ -717,6 +728,10 @@ constructor(
_shortcutAbsoluteTop.value = top
}
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ _notificationStackAbsoluteBottom.value = bottom
+ }
+
private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
return when (state) {
DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index b7d0d453779f..3a5614fbc430 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@ import android.os.Trace
import android.util.Log
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.withContextTraced as withContext
+import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -77,6 +78,8 @@ interface KeyguardTransitionRepository {
/** The [TransitionInfo] of the most recent call to [startTransition]. */
val currentTransitionInfoInternal: StateFlow<TransitionInfo>
+ /** The [TransitionInfo] of the most recent call to [startTransition]. */
+ val currentTransitionInfo: TransitionInfo
/**
* Interactors that require information about changes between [KeyguardState]s will call this to
@@ -132,7 +135,7 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
private var lastStep: TransitionStep = TransitionStep()
private var lastAnimator: ValueAnimator? = null
- private val _currentTransitionMutex = Mutex()
+ private val withContextMutex = Mutex()
private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
MutableStateFlow(
TransitionInfo(
@@ -144,6 +147,16 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
)
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
+ @Volatile
+ override var currentTransitionInfo: TransitionInfo =
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.OFF,
+ to = KeyguardState.OFF,
+ animator = null,
+ )
+ private set
+
/*
* When manual control of the transition is requested, a unique [UUID] is used as the handle
* to permit calls to [updateTransition]
@@ -163,13 +176,17 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
}
override suspend fun startTransition(info: TransitionInfo): UUID? {
- _currentTransitionInfo.value = info
+ if (transitionRaceCondition()) {
+ currentTransitionInfo = info
+ } else {
+ _currentTransitionInfo.value = info
+ }
Log.d(TAG, "(Internal) Setting current transition info: $info")
// There is no fairness guarantee with 'withContext', which means that transitions could
// be processed out of order. Use a Mutex to guarantee ordering. [updateTransition]
// requires the same lock
- _currentTransitionMutex.lock()
+ withContextMutex.lock()
// Only used in a test environment
if (forceDelayForRaceConditionTest) {
delay(50L)
@@ -177,7 +194,7 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
// Animators must be started on the main thread.
return withContext("$TAG#startTransition", mainDispatcher) {
- _currentTransitionMutex.unlock()
+ withContextMutex.unlock()
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return@withContext null
@@ -265,9 +282,9 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
// There is no fairness guarantee with 'withContext', which means that transitions could
// be processed out of order. Use a Mutex to guarantee ordering. [startTransition]
// requires the same lock
- _currentTransitionMutex.lock()
+ withContextMutex.lock()
withContext("$TAG#updateTransition", mainDispatcher) {
- _currentTransitionMutex.unlock()
+ withContextMutex.unlock()
updateTransitionInternal(transitionId, value, state)
}
@@ -302,13 +319,23 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
// Tests runs on testDispatcher, which is not the main thread, causing the animator thread
// check to fail
if (testSetup) {
- _currentTransitionInfo.value =
- TransitionInfo(
- ownerName = ownerName,
- from = KeyguardState.OFF,
- to = to,
- animator = null,
- )
+ if (transitionRaceCondition()) {
+ currentTransitionInfo =
+ TransitionInfo(
+ ownerName = ownerName,
+ from = KeyguardState.OFF,
+ to = to,
+ animator = null,
+ )
+ } else {
+ _currentTransitionInfo.value =
+ TransitionInfo(
+ ownerName = ownerName,
+ from = KeyguardState.OFF,
+ to = to,
+ animator = null,
+ )
+ }
emitTransition(
TransitionStep(
KeyguardState.OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index a7dde34e3026..8b75545fddc9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -40,6 +40,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -132,11 +133,10 @@ constructor(
scope.launch("$TAG#listenForLockscreenToDreaming") {
keyguardInteractor.isAbleToDream
.filterRelevantKeyguardState()
- .sampleCombine(
- internalTransitionInteractor.currentTransitionInfoInternal,
- transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
- )
- .collect { (isAbleToDream, transitionInfo, isOnLockscreen) ->
+ .sample(transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN), ::Pair)
+ .collect { (isAbleToDream, isOnLockscreen) ->
+ val transitionInfo =
+ internalTransitionInteractor.currentTransitionInfoInternal()
val isTransitionInterruptible =
transitionInfo.to == KeyguardState.LOCKSCREEN &&
!invalidFromStates.contains(transitionInfo.from)
@@ -179,7 +179,6 @@ constructor(
shadeRepository.legacyShadeExpansion
.sampleCombine(
transitionInteractor.startedKeyguardTransitionStep,
- internalTransitionInteractor.currentTransitionInfoInternal,
keyguardInteractor.statusBarState,
keyguardInteractor.isKeyguardDismissible,
keyguardInteractor.isKeyguardOccluded,
@@ -188,11 +187,12 @@ constructor(
(
shadeExpansion,
startedStep,
- currentTransitionInfo,
statusBarState,
isKeyguardUnlocked,
isKeyguardOccluded) ->
val id = transitionId
+ val currentTransitionInfo =
+ internalTransitionInteractor.currentTransitionInfoInternal()
if (id != null) {
if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
// An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
index 2cc6afa2f407..05078346399a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
@@ -17,13 +17,13 @@
package com.android.systemui.keyguard.domain.interactor
import android.annotation.FloatRange
+import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import java.util.UUID
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
/**
* This interactor provides direct access to [KeyguardTransitionRepository] internals and exposes
@@ -32,9 +32,7 @@ import kotlinx.coroutines.flow.StateFlow
@SysUISingleton
class InternalKeyguardTransitionInteractor
@Inject
-constructor(
- private val repository: KeyguardTransitionRepository,
-) {
+constructor(private val repository: KeyguardTransitionRepository) {
/**
* The [TransitionInfo] of the most recent call to
@@ -58,14 +56,19 @@ constructor(
* *will* be emitted, and therefore that it can safely request an AOD -> LOCKSCREEN transition
* which will subsequently cancel GONE -> AOD.
*/
- internal val currentTransitionInfoInternal: StateFlow<TransitionInfo> =
- repository.currentTransitionInfoInternal
+ internal fun currentTransitionInfoInternal(): TransitionInfo {
+ return if (transitionRaceCondition()) {
+ repository.currentTransitionInfo
+ } else {
+ repository.currentTransitionInfoInternal.value
+ }
+ }
suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) = repository.updateTransition(transitionId, value, state)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
index c19bbbce3b4b..4793d95b121c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.util.Log
+import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -51,7 +52,13 @@ constructor(
fun startDismissKeyguardTransition(reason: String = "") {
if (SceneContainerFlag.isEnabled) return
Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
- when (val startedState = repository.currentTransitionInfoInternal.value.to) {
+ val startedState =
+ if (transitionRaceCondition()) {
+ repository.currentTransitionInfo.to
+ } else {
+ repository.currentTransitionInfoInternal.value.to
+ }
+ when (startedState) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.dismissPrimaryBouncer()
ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.dismissAlternateBouncer()
@@ -61,7 +68,7 @@ constructor(
KeyguardState.GONE ->
Log.i(
TAG,
- "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
+ "Already transitioning to GONE; ignoring startDismissKeyguardTransition.",
)
else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 5f08aa320c95..631e44aca26d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -22,7 +22,7 @@ import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -74,11 +74,9 @@ constructor(
.onEach { SceneContainerFlag.assertInLegacyMode() }
// Whenever the keyguard is disabled...
.filter { enabled -> !enabled }
- .sampleCombine(
- internalTransitionInteractor.currentTransitionInfoInternal,
- biometricSettingsRepository.isCurrentUserInLockdown,
- )
- .map { (_, transitionInfo, inLockdown) ->
+ .sample(biometricSettingsRepository.isCurrentUserInLockdown, ::Pair)
+ .map { (_, inLockdown) ->
+ val transitionInfo = internalTransitionInteractor.currentTransitionInfoInternal()
// ...we hide the keyguard, if it's showing and we're not in lockdown. In that case,
// we want to remember that and re-show it when keyguard is enabled again.
transitionInfo.to != KeyguardState.GONE && !inLockdown
@@ -93,11 +91,10 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
repository.isKeyguardEnabled
.filter { enabled -> !enabled }
- .sampleCombine(
- biometricSettingsRepository.isCurrentUserInLockdown,
- internalTransitionInteractor.currentTransitionInfoInternal,
- )
- .collect { (_, inLockdown, currentTransitionInfo) ->
+ .sample(biometricSettingsRepository.isCurrentUserInLockdown, ::Pair)
+ .collect { (_, inLockdown) ->
+ val currentTransitionInfo =
+ internalTransitionInteractor.currentTransitionInfoInternal()
if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
"keyguard disabled"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 29c6d5aa5ea2..b24ca1a8d345 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -545,6 +545,10 @@ constructor(
repository.isKeyguardGoingAway.value = isGoingAway
}
+ fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ repository.setNotificationStackAbsoluteBottom(bottom)
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 7f1e881c0863..278a98f8b157 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -80,7 +80,7 @@ constructor(
// *_BOUNCER -> LOCKSCREEN.
return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
KeyguardState.deviceIsAsleepInState(
- internalTransitionInteractor.currentTransitionInfoInternal.value.to
+ internalTransitionInteractor.currentTransitionInfoInternal().to
)
}
@@ -100,13 +100,13 @@ constructor(
scene = Scenes.Gone,
stateWithoutSceneContainer = KeyguardState.GONE,
),
- ::Pair
+ ::Pair,
)
.map { (wakefulness, isOnGone) ->
wakefulness.powerButtonLaunchGestureTriggered && !isOnGone
},
// Emit false once that activity goes away.
- isShowWhenLockedActivityOnTop.filter { !it }.map { false }
+ isShowWhenLockedActivityOnTop.filter { !it }.map { false },
)
.stateIn(applicationScope, SharingStarted.Eagerly, false)
@@ -134,7 +134,7 @@ constructor(
*/
fun setWmNotifiedShowWhenLockedActivityOnTop(
showWhenLockedActivityOnTop: Boolean,
- taskInfo: RunningTaskInfo? = null
+ taskInfo: RunningTaskInfo? = null,
) {
repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
index cddeaaf27fb9..b986d56e9a82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -61,7 +61,7 @@ constructor(
fun start() {
scope.launch {
- if (internalTransitionInteractor.currentTransitionInfoInternal.value.from != OFF) {
+ if (internalTransitionInteractor.currentTransitionInfoInternal().from != OFF) {
Log.e(
"KeyguardTransitionInteractor",
"showLockscreenOnBoot emitted, but we've already " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 249982d710a7..abd7f90bbf22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -71,14 +71,14 @@ sealed class TransitionInteractor(
ownerReason: String = "",
): UUID? {
toState.checkValidState()
- if (fromState != internalTransitionInteractor.currentTransitionInfoInternal.value.to) {
+ if (fromState != internalTransitionInteractor.currentTransitionInfoInternal().to) {
Log.e(
name,
"Ignoring startTransition: This interactor asked to transition from " +
"$fromState -> $toState, but we last transitioned to " +
- "${internalTransitionInteractor.currentTransitionInfoInternal.value.to}, not" +
+ "${internalTransitionInteractor.currentTransitionInfoInternal().to}, not" +
" $fromState. This should never happen - check currentTransitionInfoInternal" +
- " or use filterRelevantKeyguardState before starting transitions."
+ " or use filterRelevantKeyguardState before starting transitions.",
)
return null
}
@@ -149,7 +149,7 @@ sealed class TransitionInteractor(
if (keyguardInteractor.isKeyguardDismissible.value) {
startTransitionTo(
KeyguardState.GONE,
- ownerReason = "Power button gesture while keyguard is dismissible"
+ ownerReason = "Power button gesture while keyguard is dismissible",
)
return true
@@ -159,7 +159,7 @@ sealed class TransitionInteractor(
// should transition to GONE.
startTransitionTo(
KeyguardState.GONE,
- ownerReason = "Power button gesture on dismissable keyguard"
+ ownerReason = "Power button gesture on dismissable keyguard",
)
return true
@@ -190,16 +190,13 @@ sealed class TransitionInteractor(
startTransitionTo(
toState = keyguardInteractor.asleepKeyguardState.value,
modeOnCanceled = modeOnCanceled,
- ownerReason = "Sleep transition triggered"
+ ownerReason = "Sleep transition triggered",
)
}
}
/** This signal may come in before the occlusion signal, and can provide a custom transition */
- fun listenForTransitionToCamera(
- scope: CoroutineScope,
- keyguardInteractor: KeyguardInteractor,
- ) {
+ fun listenForTransitionToCamera(scope: CoroutineScope, keyguardInteractor: KeyguardInteractor) {
if (!KeyguardWmStateRefactor.isEnabled) {
scope.launch {
keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
@@ -223,7 +220,7 @@ sealed class TransitionInteractor(
* [startedKeyguardState] as it does not wait for the emission of the first STARTED step.
*/
fun inOrTransitioningToRelevantKeyguardState(): Boolean {
- return internalTransitionInteractor.currentTransitionInfoInternal.value.to == fromState
+ return internalTransitionInteractor.currentTransitionInfoInternal().to == fromState
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index a09cd7c12d42..a1f606740cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,6 +19,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -65,7 +66,7 @@ constructor(
combine(
transitionInteractor.isFinishedIn(
scene = Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
wakeToGoneInteractor.canWakeDirectlyToGone,
) { isOnGone, canWakeDirectlyToGone ->
@@ -197,11 +198,11 @@ constructor(
combine(
transitionInteractor.isInTransition(
edge = Edge.create(to = Scenes.Gone),
- edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
+ edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE),
),
transitionInteractor.isFinishedIn(
scene = Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
surfaceBehindInteractor.isAnimatingSurface,
notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
@@ -231,7 +232,7 @@ constructor(
combine(
transitionInteractor.currentKeyguardState,
wakeToGoneInteractor.canWakeDirectlyToGone,
- ::Pair
+ ::Pair,
)
.sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
.map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
@@ -242,7 +243,12 @@ constructor(
startedFromStep.transitionState == TransitionState.CANCELED &&
startedFromStep.from == KeyguardState.GONE
- val transitionInfo = transitionRepository.currentTransitionInfoInternal.value
+ val transitionInfo =
+ if (transitionRaceCondition()) {
+ transitionRepository.currentTransitionInfo
+ } else {
+ transitionRepository.currentTransitionInfoInternal.value
+ }
val wakingDirectlyToGone =
deviceIsAsleepInState(transitionInfo.from) &&
transitionInfo.to == KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index 5524b20aa8f8..aa44b6d46289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -106,7 +106,7 @@ constructor(
private suspend fun handleIdle(
prevTransition: ObservableTransitionState,
- idle: ObservableTransitionState.Idle
+ idle: ObservableTransitionState.Idle,
) {
if (currentTransitionId == null) return
if (prevTransition !is ObservableTransitionState.Transition) return
@@ -133,10 +133,10 @@ constructor(
val newTransition =
TransitionInfo(
ownerName = this::class.java.simpleName,
- from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
+ from = internalTransitionInteractor.currentTransitionInfoInternal().to,
to = state,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.REVERSE
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE,
)
currentTransitionId = internalTransitionInteractor.startTransition(newTransition)
internalTransitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
@@ -152,8 +152,7 @@ constructor(
private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
if (transition.fromContent == Scenes.Lockscreen) {
if (currentTransitionId != null) {
- val currentToState =
- internalTransitionInteractor.currentTransitionInfoInternal.value.to
+ val currentToState = internalTransitionInteractor.currentTransitionInfoInternal().to
if (currentToState == UNDEFINED) {
transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
}
@@ -197,21 +196,21 @@ constructor(
from = UNDEFINED,
to = repository.nextLockscreenTargetState.value,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
repository.nextLockscreenTargetState.value = DEFAULT_STATE
startTransition(newTransition)
}
private suspend fun startTransitionFromLockscreen() {
- val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+ val currentState = internalTransitionInteractor.currentTransitionInfoInternal().to
val newTransition =
TransitionInfo(
ownerName = this::class.java.simpleName,
from = currentState,
to = UNDEFINED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
startTransition(newTransition)
}
@@ -228,7 +227,7 @@ constructor(
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
progress.coerceIn(0f, 1f),
- RUNNING
+ RUNNING,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index 12bcc7ecbab8..b15cacf077a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -35,9 +35,7 @@ import kotlinx.coroutines.flow.Flow
@SysUISingleton
class DozingToOccludedTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
@@ -56,11 +54,7 @@ constructor(
var currentAlpha = 0f
return transitionAnimation.sharedFlow(
duration = 250.milliseconds,
- startTime = if (lightRevealMigration()) {
- 100.milliseconds // Wait for the light reveal to "hit" the LS elements.
- } else {
- 0.milliseconds
- },
+ startTime = 0.milliseconds,
onStart = {
if (lightRevealMigration()) {
currentAlpha = viewState.alpha()
@@ -69,7 +63,6 @@ constructor(
}
},
onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
- onCancel = { 0f },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
index 55033f09e263..1d970349b955 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -185,6 +185,7 @@ constructor(
} else if (isOnKeyguard && !unlocking && isDreaming) {
Model(scrimState = ScrimState.DREAMING, unlocking = false)
} else {
+ onKeyguardFadedAway(transitionState.isIdle(Scenes.Gone))
Model(scrimState = ScrimState.UNLOCKED, unlocking = unlocking)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
index c74f038ebea4..38de17eb7fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
@@ -22,7 +22,7 @@ import com.android.systemui.res.R
import javax.inject.Inject
import kotlin.math.max
-class LargeScreenHeaderHelper @Inject constructor(private val context: Context) {
+class LargeScreenHeaderHelper @Inject constructor(@ShadeDisplayAware private val context: Context) {
fun getLargeScreenHeaderHeight(): Int = getLargeScreenHeaderHeight(context)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index f463cb5bcbd6..6e63446d88d8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -34,7 +34,7 @@ import javax.inject.Inject
class NotificationPanelUnfoldAnimationController
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
statusBarStateController: StatusBarStateController,
progressProvider: NaturalRotationUnfoldProgressProvider,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 67d162b4aa1b..083cf1fc8b17 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -668,7 +668,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@Main Handler handler,
- LayoutInflater layoutInflater,
+ @ShadeDisplayAware LayoutInflater layoutInflater,
FeatureFlags featureFlags,
NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler pulseExpansionHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 5b6696bb60df..24dba59a1d54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -148,7 +148,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Inject
public NotificationShadeWindowControllerImpl(
- Context context,
+ @ShadeDisplayAware Context context,
WindowRootViewComponent.Factory windowRootViewComponentFactory,
ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
IActivityManager activityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
index 91627d63980e..7a70966c2b12 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
@@ -14,7 +14,7 @@ import javax.inject.Inject
class QsBatteryModeController
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
insetsProviderStore: StatusBarContentInsetsProviderStore,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 1918094cceee..15b22700072f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -83,7 +83,7 @@ abstract class ShadeViewProviderModule {
@Provides
@SysUISingleton
fun providesWindowRootView(
- layoutInflater: LayoutInflater,
+ @ShadeDisplayAware layoutInflater: LayoutInflater,
viewModelFactory: SceneContainerViewModel.Factory,
containerConfigProvider: Provider<SceneContainerConfig>,
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
@@ -156,7 +156,7 @@ abstract class ShadeViewProviderModule {
@Provides
fun providesKeyguardBottomAreaView(
npv: NotificationPanelView,
- layoutInflater: LayoutInflater,
+ @ShadeDisplayAware layoutInflater: LayoutInflater,
): KeyguardBottomAreaView {
return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
as KeyguardBottomAreaView
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index a171d33ddb47..4b8cc00e1c28 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
@@ -153,7 +154,7 @@ public class ShadeCarrierGroupController {
ShadeCarrierGroupControllerLogger logger,
NetworkController networkController,
CarrierTextManager.Builder carrierTextManagerBuilder,
- Context context,
+ @ShadeDisplayAware Context context,
CarrierConfigTracker carrierConfigTracker,
SlotIndexResolver slotIndexResolver,
MobileUiAdapter mobileUiAdapter,
@@ -497,7 +498,7 @@ public class ShadeCarrierGroupController {
ShadeCarrierGroupControllerLogger logger,
NetworkController networkController,
CarrierTextManager.Builder carrierTextControllerBuilder,
- Context context,
+ @ShadeDisplayAware Context context,
CarrierConfigTracker carrierConfigTracker,
SlotIndexResolver slotIndexResolver,
MobileUiAdapter mobileUiAdapter,
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 193056c19d4e..5629938deeb0 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
@@ -182,7 +182,7 @@ interface ShadeRepository {
/** Business logic for shade interactions */
@SysUISingleton
-class ShadeRepositoryImpl @Inject constructor(@Application applicationContext: Context) :
+class ShadeRepositoryImpl @Inject constructor() :
ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
@Deprecated("Use ShadeInteractor.qsExpansion instead")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 6c44c7315fb9..e5d08a0ac977 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -26,6 +26,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -48,7 +49,7 @@ class ShadeStartable
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Application private val applicationContext: Context,
+ @ShadeDisplayAware private val context: Context,
@ShadeTouchLog private val touchLog: LogBuffer,
private val configurationRepository: ConfigurationRepository,
private val shadeRepository: ShadeRepository,
@@ -94,7 +95,7 @@ constructor(
// Force initial collection.
.onStart { emit(Unit) }
.collect {
- val resources = applicationContext.resources
+ val resources = context.resources
// The configuration for 'shouldUseSplitNotificationShade' dictates the width of
// the shade in both split-shade and dual-shade modes.
shadeRepository.setShadeLayoutWide(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
index 823742af49d2..a5caa0963bc0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -30,7 +30,7 @@ internal class LargeScreenShadeInterpolatorImpl
@Inject
internal constructor(
@ShadeDisplayAware configurationController: ConfigurationController,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val splitShadeInterpolator: SplitShadeInterpolator,
private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
private val splitShadeStateController: SplitShadeStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 5be5ccce7c98..45516aa69cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -53,7 +54,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch
class ShadeHeaderViewModel
@AssistedInject
constructor(
- context: Context,
+ @ShadeDisplayAware context: Context,
private val activityStarter: ActivityStarter,
private val shadeInteractor: ShadeInteractor,
private val mobileIconsInteractor: MobileIconsInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
index 321b6084831e..31022d578b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -128,7 +128,8 @@ public class RemoteInputNotificationRebuilder {
// Read the whole remoteInputs list from the entry, then append all of those to the sbn.
Parcelable[] oldHistoryItems = sbn.getNotification().extras
- .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ RemoteInputHistoryItem.class);
RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
? Stream.concat(
@@ -144,7 +145,8 @@ public class RemoteInputNotificationRebuilder {
? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
: new RemoteInputHistoryItem(remoteInputText);
Parcelable[] oldHistoryItems = sbn.getNotification().extras
- .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ RemoteInputHistoryItem.class);
RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
? Stream.concat(
Stream.of(newItem),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 682a9fff2994..77ec65bfad6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -29,6 +29,7 @@ import com.android.internal.widget.MessagingLayout
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -141,7 +142,7 @@ class ConversationNotificationManager
@Inject
constructor(
bindEventManager: BindEventManager,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val notifCollection: CommonNotifCollection,
@Main private val mainHandler: Handler
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
new file mode 100644
index 000000000000..48a8c01e7c47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.collection
+
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats
+
+/**
+ * A holder class for a [NotificationEntry] and an associated [DismissedByUserStats], used by
+ * [NotifCollection] for handling dismissal.
+ */
+data class EntryWithDismissStats(val entry: NotificationEntry, val stats: DismissedByUserStats) {
+ /**
+ * Creates deep a copy of this object, but with the entry, key and rank updated to correspond to
+ * the given entry.
+ */
+ fun copyForEntry(newEntry: NotificationEntry) =
+ EntryWithDismissStats(
+ entry = newEntry,
+ stats =
+ DismissedByUserStats(
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ NotificationVisibility.obtain(
+ newEntry.key,
+ newEntry.ranking.rank,
+ stats.notificationVisibility.count,
+ /* visible= */ false,
+ ),
+ ),
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index b5c6c252e50f..cf9ee619b734 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -63,14 +63,12 @@ import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -276,7 +274,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
* Dismisses multiple notifications on behalf of the user.
*/
public void dismissNotifications(
- List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
+ List<EntryWithDismissStats> entriesToDismiss) {
Assert.isMainThread();
checkForReentrantCall();
@@ -287,8 +285,8 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
final int entryCount = entriesToDismiss.size();
final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
for (int i = 0; i < entriesToDismiss.size(); i++) {
- NotificationEntry entry = entriesToDismiss.get(i).first;
- DismissedByUserStats stats = entriesToDismiss.get(i).second;
+ NotificationEntry entry = entriesToDismiss.get(i).getEntry();
+ DismissedByUserStats stats = entriesToDismiss.get(i).getStats();
requireNonNull(stats);
NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
@@ -343,31 +341,20 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
dispatchEventsAndRebuildList("dismissNotifications");
}
- private List<Pair<NotificationEntry, DismissedByUserStats>> includeSummariesToDismiss(
- List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
+ private List<EntryWithDismissStats> includeSummariesToDismiss(
+ List<EntryWithDismissStats> entriesToDismiss) {
final HashSet<NotificationEntry> entriesSet = new HashSet<>(entriesToDismiss.size());
- for (Pair<NotificationEntry, DismissedByUserStats> entryToStats : entriesToDismiss) {
- entriesSet.add(entryToStats.first);
+ for (EntryWithDismissStats entryToStats : entriesToDismiss) {
+ entriesSet.add(entryToStats.getEntry());
}
- final List<Pair<NotificationEntry, DismissedByUserStats>> entriesPlusSummaries =
+ final List<EntryWithDismissStats> entriesPlusSummaries =
new ArrayList<>(entriesToDismiss.size() + 1);
- for (Pair<NotificationEntry, DismissedByUserStats> entryToStats : entriesToDismiss) {
+ for (EntryWithDismissStats entryToStats : entriesToDismiss) {
entriesPlusSummaries.add(entryToStats);
- NotificationEntry summary = fetchSummaryToDismiss(entryToStats.first);
+ NotificationEntry summary = fetchSummaryToDismiss(entryToStats.getEntry());
if (summary != null && !entriesSet.contains(summary)) {
- DismissedByUserStats currentStats = entryToStats.second;
- NotificationVisibility summaryVisibility = NotificationVisibility.obtain(
- summary.getKey(),
- summary.getRanking().getRank(),
- currentStats.notificationVisibility.count,
- /* visible= */ false);
- DismissedByUserStats summaryStats = new DismissedByUserStats(
- currentStats.dismissalSurface,
- currentStats.dismissalSentiment,
- summaryVisibility
- );
- entriesPlusSummaries.add(new Pair<>(summary, summaryStats));
+ entriesPlusSummaries.add(entryToStats.copyForEntry(summary));
}
}
return entriesPlusSummaries;
@@ -379,7 +366,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
public void dismissNotification(
NotificationEntry entry,
@NonNull DismissedByUserStats stats) {
- dismissNotifications(List.of(new Pair<>(entry, stats)));
+ dismissNotifications(List.of(new EntryWithDismissStats(entry, stats)));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index e74ed8d27ec2..205c42f3c2f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -586,7 +586,8 @@ public final class NotificationEntry extends ListEntry {
}
Bundle extras = mSbn.getNotification().extras;
Parcelable[] replyTexts =
- extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ RemoteInputHistoryItem.class);
if (!ArrayUtils.isEmpty(replyTexts)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index df694bb67684..de868d45e64f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.content.Context
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -33,7 +34,7 @@ import javax.inject.Inject
*/
@CoordinatorScope
class RowAppearanceCoordinator @Inject internal constructor(
- context: Context,
+ @ShadeDisplayAware context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
private var mSectionStyleProvider: SectionStyleProvider
) : Coordinator {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index b8a959440312..db778b801dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Compile
import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/**
@@ -39,7 +40,7 @@ import javax.inject.Inject
*/
@CoordinatorScope
class ViewConfigCoordinator @Inject internal constructor(
- private val mConfigurationController: ConfigurationController,
+ @ShadeDisplayAware private val mConfigurationController: ConfigurationController,
private val mLockscreenUserManager: NotificationLockscreenUserManager,
private val mGutsManager: NotificationGutsManager,
private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 9d5d7a19916a..e6d22b07f3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -37,6 +37,7 @@ import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -86,7 +87,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
@Inject
public NotificationRowBinderImpl(
- Context context,
+ @ShadeDisplayAware Context context,
NotificationMessagingUtil notificationMessagingUtil,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationLockscreenUserManager notificationLockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 193586679d23..cab4c1c88b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -36,7 +37,7 @@ import dagger.assisted.AssistedInject
* currently populate the notification shade.
*/
class ShadeViewManager @AssistedInject constructor(
- context: Context,
+ @ShadeDisplayAware context: Context,
@Assisted listContainer: NotificationListContainer,
@Assisted private val stackController: NotifStackController,
mediaContainerController: MediaContainerController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 5a616dfd1f63..e5ce25d5144e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -40,6 +40,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.settingslib.Utils;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
@@ -59,6 +60,9 @@ public class FooterView extends StackScrollerDecorView {
private FooterViewButton mClearAllButton;
private FooterViewButton mManageOrHistoryButton;
+ // The settings & history buttons replace the single manage/history button in the redesign
+ private FooterViewButton mSettingsButton;
+ private FooterViewButton mHistoryButton;
private boolean mShouldBeHidden;
private boolean mShowHistory;
// String cache, for performance reasons.
@@ -269,7 +273,12 @@ public class FooterView extends StackScrollerDecorView {
}
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
- mManageOrHistoryButton = findViewById(R.id.manage_text);
+ if (Flags.notificationsRedesignFooterView()) {
+ mSettingsButton = findViewById(R.id.settings_button);
+ mHistoryButton = findViewById(R.id.history_button);
+ } else {
+ mManageOrHistoryButton = findViewById(R.id.manage_text);
+ }
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
if (!FooterViewRefactor.isEnabled()) {
updateResources();
@@ -342,8 +351,10 @@ public class FooterView extends StackScrollerDecorView {
updateClearAllButtonText();
updateClearAllButtonDescription();
- updateManageOrHistoryButtonText();
- updateManageOrHistoryButtonDescription();
+ if (!Flags.notificationsRedesignFooterView()) {
+ updateManageOrHistoryButtonText();
+ updateManageOrHistoryButtonDescription();
+ }
updateMessageString();
updateMessageIcon();
@@ -420,8 +431,16 @@ public class FooterView extends StackScrollerDecorView {
}
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
- mManageOrHistoryButton.setBackground(manageBg);
- mManageOrHistoryButton.setTextColor(onSurface);
+ if (Flags.notificationsRedesignFooterView()) {
+ mSettingsButton.setBackground(manageBg);
+ mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
+
+ mHistoryButton.setBackground(manageBg);
+ mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
+ } else {
+ mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setTextColor(onSurface);
+ }
mSeenNotifsFooterTextView.setTextColor(onSurface);
mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 2ec7f532d8c3..ddd9cdd5e306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.footer.ui.viewbinder
import android.view.View
import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
@@ -63,14 +64,16 @@ object FooterViewBinder {
notificationActivityStarter: NotificationActivityStarter,
) = coroutineScope {
launch { bindClearAllButton(footer, viewModel, clearAllNotifications) }
- launch {
- bindManageOrHistoryButton(
- footer,
- viewModel,
- launchNotificationSettings,
- launchNotificationHistory,
- notificationActivityStarter,
- )
+ if (!Flags.notificationsRedesignFooterView()) {
+ launch {
+ bindManageOrHistoryButton(
+ footer,
+ viewModel,
+ launchNotificationSettings,
+ launchNotificationHistory,
+ notificationActivityStarter,
+ )
+ }
}
launch { bindMessage(footer, viewModel) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index d4466f8771a6..71cddc99b564 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -29,6 +29,7 @@ import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
@@ -72,7 +73,7 @@ constructor(
private val systemSettings: SystemSettings,
private val packageManager: PackageManager,
private val bubbles: Optional<Bubbles>,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val notificationManager: NotificationManager,
private val settingsInteractor: NotificationSettingsInteractor
) : VisualInterruptionDecisionProvider {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index 068f23d4284c..e233deffe42f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Empty
import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.FullImage
import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Initial
@@ -61,7 +62,7 @@ private const val FREE_IMAGE_DELAY_MS = 3000L
class BigPictureIconManager
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val imageLoader: ImageLoader,
private val statsManager: BigPictureStatsManager,
@Application private val scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 172b76cbcca9..98d704c75d33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -33,6 +33,7 @@ import com.android.launcher3.util.UserIconInfo
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
import com.android.systemui.util.asIndenting
import com.android.systemui.util.withIncreasedIndent
@@ -67,7 +68,7 @@ interface AppIconProvider {
@SysUISingleton
class AppIconProviderImpl
@Inject
-constructor(private val sysuiContext: Context, dumpManager: DumpManager) :
+constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: DumpManager) :
AppIconProvider, Dumpable {
init {
dumpManager.registerNormalDumpable(TAG, this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 7441c7058d7b..c9a0010c0de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -20,6 +20,7 @@ import android.util.Log
import android.view.View
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
@@ -41,7 +42,7 @@ import javax.inject.Inject
class NotificationSectionsManager
@Inject
internal constructor(
- private val configurationController: ConfigurationController,
+ @ShadeDisplayAware private val configurationController: ConfigurationController,
private val keyguardMediaController: KeyguardMediaController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val mediaContainerController: MediaContainerController,
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 87b16efc64fc..1d4916c6cd82 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
@@ -1200,7 +1200,6 @@ public class NotificationStackScrollLayout
if (!SceneContainerFlag.isEnabled()) {
setMaxLayoutHeight(getHeight());
updateContentHeight();
- mWallpaperInteractor.setNotificationStackAbsoluteBottom(mContentHeight);
}
clampScrollPosition();
requestChildrenUpdate();
@@ -1278,7 +1277,6 @@ public class NotificationStackScrollLayout
if (mAmbientState.getStackTop() != stackTop) {
mAmbientState.setStackTop(stackTop);
onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
- mWallpaperInteractor.setNotificationStackAbsoluteBottom((int) stackTop);
}
}
@@ -2648,6 +2646,7 @@ public class NotificationStackScrollLayout
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
+
mContentHeight =
(int) (height + Math.max(getIntrinsicPadding(), getTopPadding()) + mBottomPadding);
updateScrollability();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 04974b4353f4..400654698d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -43,7 +43,6 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
-import android.util.Pair;
import android.util.Property;
import android.view.Display;
import android.view.MotionEvent;
@@ -105,6 +104,7 @@ import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNot
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -1777,12 +1777,12 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mNotifCollection.dismissAllNotifications(
mLockscreenUserManager.getCurrentUserId());
} else {
- final List<Pair<NotificationEntry, DismissedByUserStats>>
+ final List<EntryWithDismissStats>
entriesWithRowsDismissedFromShade = new ArrayList<>();
for (ExpandableNotificationRow row : viewsToRemove) {
final NotificationEntry entry = row.getEntry();
entriesWithRowsDismissedFromShade.add(
- new Pair<>(entry, getDismissedByUserStats(entry)));
+ new EntryWithDismissStats(entry, getDismissedByUserStats(entry)));
}
mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 6042964281c2..4894ad3f1192 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -25,6 +25,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.policy.SplitShadeStateController
import dagger.Lazy
@@ -45,7 +46,7 @@ import kotlinx.coroutines.flow.map
class SharedNotificationContainerInteractor
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val splitShadeStateController: Lazy<SplitShadeStateController>,
private val shadeInteractor: Lazy<ShadeInteractor>,
configurationInteractor: ConfigurationInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 787ff024a4d6..fd19f1ff634e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -22,6 +22,7 @@ import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.TraceUtils.traceAsync
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.nano.MetricsProto
+import com.android.systemui.Flags
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
import com.android.systemui.dagger.qualifiers.Background
@@ -145,7 +146,9 @@ constructor(
// The footer needs to be re-inflated every time the theme or the font size changes.
configuration
.inflateLayout<FooterView>(
- R.layout.status_bar_notification_footer,
+ if (Flags.notificationsRedesignFooterView())
+ R.layout.status_bar_notification_footer_redesign
+ else R.layout.status_bar_notification_footer,
parentView,
attachToRoot = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 54b5ca37c43e..ce89d786c350 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,10 +18,12 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags
import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -48,8 +50,19 @@ constructor(
private val notificationScrollViewBinder: NotificationScrollViewBinder,
private val communalSettingsInteractor: CommunalSettingsInteractor,
@Main private val mainImmediateDispatcher: CoroutineDispatcher,
+ val keyguardInteractor: KeyguardInteractor,
) {
+ private val calculateMaxNotifications: (Float, Boolean) -> Int = { space, extraShelfSpace ->
+ val shelfHeight = controller.getShelfHeight().toFloat()
+ notificationStackSizeCalculator.computeMaxKeyguardNotifications(
+ controller.view,
+ space,
+ if (extraShelfSpace) shelfHeight else 0f,
+ shelfHeight,
+ )
+ }
+
fun bind(
view: SharedNotificationContainer,
viewModel: SharedNotificationContainerViewModel,
@@ -107,17 +120,9 @@ constructor(
}
launch {
- viewModel
- .getMaxNotifications { space, extraShelfSpace ->
- val shelfHeight = controller.getShelfHeight().toFloat()
- notificationStackSizeCalculator.computeMaxKeyguardNotifications(
- controller.getView(),
- space,
- if (extraShelfSpace) shelfHeight else 0f,
- shelfHeight,
- )
- }
- .collect { controller.setMaxDisplayedNotifications(it) }
+ viewModel.getMaxNotifications(calculateMaxNotifications).collect {
+ controller.setMaxDisplayedNotifications(it)
+ }
}
if (!SceneContainerFlag.isEnabled) {
@@ -136,6 +141,30 @@ constructor(
}
}
+ if (!SceneContainerFlag.isEnabled) {
+ if (Flags.magicPortraitWallpapers()) {
+ launch {
+ viewModel
+ .getNotificationStackAbsoluteBottom(
+ calculateMaxNotifications = calculateMaxNotifications,
+ calculateHeight = { maxNotifications ->
+ notificationStackSizeCalculator.computeHeight(
+ maxNotifs = maxNotifications,
+ shelfHeight = controller.getShelfHeight().toFloat(),
+ stack = controller.view,
+ )
+ },
+ controller.getShelfHeight().toFloat(),
+ )
+ .collect { bottom ->
+ keyguardInteractor.setNotificationStackAbsoluteBottom(
+ bottom
+ )
+ }
+ }
+ }
+ }
+
launch { viewModel.translationX.collect { x -> controller.translationX = x } }
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 9515029962ea..eeebac06206c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -221,13 +221,15 @@ constructor(
/** If the user is visually on one of the unoccluded lockscreen states. */
val isOnLockscreen: Flow<Boolean> =
anyOf(
- keyguardTransitionInteractor.isFinishedIn(AOD),
- keyguardTransitionInteractor.isFinishedIn(DOZING),
- keyguardTransitionInteractor.isFinishedIn(ALTERNATE_BOUNCER),
- keyguardTransitionInteractor.isFinishedIn(
- scene = Scenes.Bouncer,
- stateWithoutSceneContainer = PRIMARY_BOUNCER,
- ),
+ keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
+ keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
+ keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f },
+ keyguardTransitionInteractor
+ .transitionValue(
+ scene = Scenes.Bouncer,
+ stateWithoutSceneContainer = PRIMARY_BOUNCER,
+ )
+ .map { it > 0f },
keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
)
.flowName("isOnLockscreen")
@@ -672,6 +674,36 @@ constructor(
.dumpWhileCollecting("maxNotifications")
}
+ /**
+ * Wallpaper needs the absolute bottom of notification stack to avoid occlusion
+ *
+ * @param calculateMaxNotifications is required by getMaxNotifications as calculateSpace by
+ * calling computeMaxKeyguardNotifications in NotificationStackSizeCalculator
+ * @param calculateHeight is calling computeHeight in NotificationStackSizeCalculator The edge
+ * case is that when maxNotifications is 0, we won't take shelfHeight into account
+ */
+ fun getNotificationStackAbsoluteBottom(
+ calculateMaxNotifications: (Float, Boolean) -> Int,
+ calculateHeight: (Int) -> Float,
+ shelfHeight: Float,
+ ): Flow<Float> {
+ SceneContainerFlag.assertInLegacyMode()
+
+ return combine(
+ getMaxNotifications(calculateMaxNotifications).map {
+ val height = calculateHeight(it)
+ if (it == 0) {
+ height - shelfHeight
+ } else {
+ height
+ }
+ },
+ bounds.map { it.top },
+ ) { height, top ->
+ top + height
+ }
+ }
+
fun notificationStackChanged() {
interactor.notificationStackChanged()
}
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 b469e3db41e5..65663fd3f19c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -646,6 +646,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
NavigationBarController navigationBarController,
AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
+ // TODO: b/374267505 - Decouple the config change needed for shade window classes from
+ // the one for other windows.
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
@@ -1975,6 +1977,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
* meantime, just update the things that we know change.
*/
void updateResources() {
+ // TODO: b/374267505 - we shouldn't propagate this from here. Each class should be
+ // listening at the correct configuration change. For example, shade window classes should
+ // be listening at @ShadeDisplayAware configurations (as it can be on a different display.
+
// Update the quick setting tiles
if (mQSPanelController != null) {
mQSPanelController.updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index 54953c9c2574..9055d18a9f55 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -35,6 +35,4 @@ class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
override var rootView: View? = null
-
- override fun setNotificationStackAbsoluteBottom(bottom: Float) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 0f6e96066cd3..015b480eddc8 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -45,7 +45,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -64,12 +63,6 @@ interface WallpaperRepository {
/** Set rootView to get its windowToken afterwards */
var rootView: View?
-
- /**
- * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
- * this value
- */
- fun setNotificationStackAbsoluteBottom(bottom: Float)
}
@SysUISingleton
@@ -106,7 +99,8 @@ constructor(
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
/** The bottom of notification stack respect to the top of screen. */
- private val notificationStackAbsoluteBottom: MutableStateFlow<Float> = MutableStateFlow(0F)
+ private val notificationStackAbsoluteBottom: StateFlow<Float> =
+ keyguardRepository.notificationStackAbsoluteBottom
/** The top of shortcut respect to the top of screen. */
private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop
@@ -206,10 +200,6 @@ constructor(
initialValue = false,
)
- override fun setNotificationStackAbsoluteBottom(bottom: Float) {
- notificationStackAbsoluteBottom.value = bottom
- }
-
private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
return withContext(bgDispatcher) {
wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
index fe6977c367b5..88795cada716 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
@@ -21,10 +21,6 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
class WallpaperInteractor @Inject constructor(val wallpaperRepository: WallpaperRepository) {
- fun setNotificationStackAbsoluteBottom(bottom: Float) {
- wallpaperRepository.setNotificationStackAbsoluteBottom(bottom)
- }
-
val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
wallpaperRepository.wallpaperSupportsAmbientMode
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 3893c9b05bd3..e21a005b8ada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -72,7 +72,6 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -1292,8 +1291,8 @@ public class NotifCollectionTest extends SysuiTestCase {
// WHEN both notifications are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair<>(entry1, defaultStats(entry1)),
- new Pair<>(entry2, defaultStats(entry2))));
+ List.of(entryWithDefaultStats(entry1),
+ entryWithDefaultStats(entry2)));
// THEN build list is only called one time
verifyBuiltList(List.of(entry1, entry2));
@@ -1311,8 +1310,8 @@ public class NotifCollectionTest extends SysuiTestCase {
DismissedByUserStats stats1 = defaultStats(entry1);
DismissedByUserStats stats2 = defaultStats(entry2);
mCollection.dismissNotifications(
- List.of(new Pair<>(entry1, defaultStats(entry1)),
- new Pair<>(entry2, defaultStats(entry2))));
+ List.of(entryWithDefaultStats(entry1),
+ entryWithDefaultStats(entry2)));
// THEN we send the dismissals to system server
FakeExecutor.exhaustExecutors(mBgExecutor);
@@ -1343,8 +1342,8 @@ public class NotifCollectionTest extends SysuiTestCase {
// WHEN both notifications are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair<>(entry1, defaultStats(entry1)),
- new Pair<>(entry2, defaultStats(entry2))));
+ List.of(entryWithDefaultStats(entry1),
+ entryWithDefaultStats(entry2)));
// THEN the entries are marked as dismissed
assertEquals(DISMISSED, entry1.getDismissState());
@@ -1368,8 +1367,8 @@ public class NotifCollectionTest extends SysuiTestCase {
// WHEN both notifications are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair<>(entry1, defaultStats(entry1)),
- new Pair<>(entry2, defaultStats(entry2))));
+ List.of(entryWithDefaultStats(entry1),
+ entryWithDefaultStats(entry2)));
// THEN all interceptors get checked
verify(mInterceptor1).shouldInterceptDismissal(entry1);
@@ -1409,8 +1408,8 @@ public class NotifCollectionTest extends SysuiTestCase {
// WHEN one child from each group are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair<>(entry1child, defaultStats(entry1child)),
- new Pair<>(entry2child1, defaultStats(entry2child1))));
+ List.of(entryWithDefaultStats(entry1child),
+ entryWithDefaultStats(entry2child1)));
// THEN the summary for the singleton child is dismissed, but not the other summary
verify(mInterceptor1).shouldInterceptDismissal(entry1summary);
@@ -1806,6 +1805,10 @@ public class NotifCollectionTest extends SysuiTestCase {
NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
}
+ private static EntryWithDismissStats entryWithDefaultStats(NotificationEntry entry) {
+ return new EntryWithDismissStats(entry, defaultStats(entry));
+ }
+
private CollectionEvent postNotif(NotificationEntryBuilder builder) {
clearInvocations(mCollectionListener);
NotifEvent rawEvent = mNoMan.postNotif(builder);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 72d1db3affe8..31c650d861ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
@@ -372,6 +373,29 @@ public class NotificationEntryTest extends SysuiTestCase {
}
@Test
+ public void notificationDataEntry_testIsLastMessageFromReply_invalidData() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ new NotificationChannelGroup[]{});
+
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addExtras(bundle)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("pkg")
+ .setOpPkg("pkg")
+ .setTag("tag")
+ .setNotification(notification)
+ .setUser(mContext.getUser())
+ .setOverrideGroupKey("")
+ .build();
+ entry.setHasSentReply();
+
+ assertFalse(entry.isLastMessageFromReply());
+ }
+
+ @Test
public void notificationDataEntry_testIsLastMessageFromReply_invalidPerson_noCrash() {
Person.Builder person = new Person.Builder()
.setName("name")
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 08786495eca4..693ec7954d70 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
@@ -131,6 +131,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val shortcutAbsoluteTop: StateFlow<Float>
get() = _shortcutAbsoluteTop.asStateFlow()
+ private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
+ override val notificationStackAbsoluteBottom: StateFlow<Float>
+ get() = _notificationStackAbsoluteBottom.asStateFlow()
+
private val _isKeyguardEnabled = MutableStateFlow(true)
override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
@@ -294,6 +298,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_shortcutAbsoluteTop.value = top
}
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ _notificationStackAbsoluteBottom.value = bottom
+ }
+
override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
_canIgnoreAuthAndReturnToGone.value = canWake
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 70b4f79131a7..4976cc26068a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.annotation.FloatRange
+import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -88,6 +89,13 @@ class FakeKeyguardTransitionRepository(
)
)
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
+ override var currentTransitionInfo =
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
+ animator = null,
+ )
init {
// Seed with a FINISHED transition in OFF, same as the real repository.
@@ -261,8 +269,13 @@ class FakeKeyguardTransitionRepository(
validateStep: Boolean = true,
) {
if (step.transitionState == TransitionState.STARTED) {
- _currentTransitionInfo.value =
- TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
+ if (transitionRaceCondition()) {
+ currentTransitionInfo =
+ TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
+ } else {
+ _currentTransitionInfo.value =
+ TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
+ }
}
_transitions.replayCache.last().let { lastStep ->
@@ -308,7 +321,11 @@ class FakeKeyguardTransitionRepository(
}
override suspend fun startTransition(info: TransitionInfo): UUID? {
- _currentTransitionInfo.value = info
+ if (transitionRaceCondition()) {
+ currentTransitionInfo = info
+ } else {
+ _currentTransitionInfo.value = info
+ }
if (sendTransitionStepsOnStartTransition) {
sendTransitionSteps(from = info.from, to = info.to, testScope = testScope)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
index a1f157f13210..10534a02bfe7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -40,7 +40,7 @@ val Kosmos.shadeExpansionStateManager by Fixture { ShadeExpansionStateManager()
val Kosmos.shadeStartable by Fixture {
ShadeStartable(
applicationScope = applicationCoroutineScope,
- applicationContext = applicationContext,
+ context = applicationContext,
touchLog = mock<LogBuffer>(),
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 478bead1354f..e0f9ec94a819 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -25,6 +25,7 @@ import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runners.model.TestClass;
@@ -134,8 +135,17 @@ public class RavenwoodAwareTestRunnerHook {
if (scope == Scope.Instance && order == Order.Outer) {
// End of a test method.
runner.mState.exitTestMethod();
- RavenwoodTestStats.getInstance().onTestFinished(classDescription, description,
- th == null ? Result.Passed : Result.Failed);
+
+ final Result result;
+ if (th == null) {
+ result = Result.Passed;
+ } else if (th instanceof AssumptionViolatedException) {
+ result = Result.Skipped;
+ } else {
+ result = Result.Failed;
+ }
+
+ RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
}
// If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/scripts/ravenwood-test-summary b/ravenwood/scripts/ravenwood-test-summary
new file mode 100755
index 000000000000..602fbff31ea3
--- /dev/null
+++ b/ravenwood/scripts/ravenwood-test-summary
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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.
+
+'''
+Print the latest Ravenwood test execution summary
+
+Usage: /ravenwood-test-summary
+
+Example output:
+Module Passed Failed Skipped
+android.test.mock.ravenwood.tests 2 0 0
+CarLibHostUnitTest 565 0 7
+CarServiceHostUnitTest 364 0 0
+CtsAccountManagerTestCasesRavenwood 4 0 0
+CtsAppTestCasesRavenwood 21 0 0
+
+Description:
+This script finds all the test execution result from /tmp/Ravenwood-stats*,
+and shows per-module summary.
+'''
+
+import csv
+import glob
+import sys
+
+# Find the latest stats files.
+stats_files = glob.glob('/tmp/Ravenwood-stats_*_latest.csv')
+
+if len(stats_files) == 0:
+ print("No log files found.", file=sys.stderr)
+ exit(1)
+
+
+def parse_stats(file, result):
+ module = '(unknwon)'
+ passed = 0
+ failed = 0
+ skipped = 0
+ with open(file) as csvfile:
+ reader = csv.reader(csvfile, delimiter=',')
+
+
+ for i, row in enumerate(reader):
+ if i == 0: continue # Skip header line
+ module = row[0]
+ passed += int(row[3])
+ failed += int(row[4])
+ skipped += int(row[5])
+
+ result[module] = (passed, failed, skipped)
+
+
+result = {}
+
+for file in stats_files:
+ parse_stats(file, result)
+
+print('%-60s %8s %8s %8s' % ("Module", "Passed", "Failed", "Skipped"))
+
+for module in sorted(result.keys(), key=str.casefold):
+ r = result[module]
+ print('%-60s %8d %8d %8d' % (module, r[0], r[1], r[2]))
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e03045c06ce..87ce649474a5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2772,8 +2772,12 @@ public class ActivityManagerService extends IActivityManager.Stub
// Add common services.
// IMPORTANT: Before adding services here, make sure ephemeral apps can access them too.
// Enable the check in ApplicationThread.bindApplication() to make sure.
- if (!android.server.Flags.removeJavaServiceManagerCache()) {
- addServiceToMap(mAppBindArgs, "permissionmgr");
+
+ // Removing User Service and App Ops Service from cache breaks boot for auto.
+ // Removing permissionmgr breaks tests for Android Auto due to SELinux restrictions.
+ // TODO: fix SELinux restrictions and remove caching for Android Auto.
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ || !android.server.Flags.removeJavaServiceManagerCache()) {
addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE);
addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE);
addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE);
@@ -2790,16 +2794,16 @@ public class ActivityManagerService extends IActivityManager.Stub
addServiceToMap(mAppBindArgs, Context.POWER_SERVICE);
addServiceToMap(mAppBindArgs, "mount");
addServiceToMap(mAppBindArgs, Context.PLATFORM_COMPAT_SERVICE);
+ addServiceToMap(mAppBindArgs, "permissionmgr");
+ addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
}
// See b/79378449
// Getting the window service and package service binder from servicemanager
// is blocked for Apps. However they are necessary for apps.
- // Removing User Service and App Ops Service from cache breaks boot for auto.
// TODO: remove exception
- addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
addServiceToMap(mAppBindArgs, "package");
addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
- addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
}
return mAppBindArgs;
}
@@ -5551,6 +5555,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (target instanceof PendingIntentRecord) {
final PendingIntentRecord originalRecord = (PendingIntentRecord) target;
+ addCreatorToken(intent, originalRecord.getPackageName());
+
// In multi-display scenarios, there can be background users who execute the
// PendingIntent. In these scenarios, we don't want to use the foreground user as the
// current user.
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index e40d855293cd..1c5bd59fa386 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -127,6 +127,13 @@ public abstract class InputManagerInternal {
*/
public abstract void notifyInputMethodConnectionActive(boolean connectionIsActive);
+ /**
+ * Notify user id changes to input.
+ *
+ * TODO(b/362473586): Cleanup after input shifts to Lifecycle with user change callbacks
+ */
+ public abstract void setCurrentUser(@UserIdInt int newUserId);
+
/** Callback interface for notifications relating to the lid switch. */
public interface LidSwitchCallback {
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bea520f9429e..a421d044507a 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -175,6 +175,7 @@ public class InputManagerService extends IInputManager.Stub
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+ private static final int MSG_CURRENT_USER_CHANGED = 4;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
private static final AdditionalDisplayInputProperties
@@ -184,6 +185,8 @@ public class InputManagerService extends IInputManager.Stub
private final Context mContext;
private final InputManagerHandler mHandler;
+ @UserIdInt
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
private DisplayManagerInternal mDisplayManagerInternal;
private WindowManagerInternal mWindowManagerInternal;
@@ -2982,6 +2985,10 @@ public class InputManagerService extends IInputManager.Stub
mKeyGestureController.unregisterKeyGestureHandler(handler, Binder.getCallingPid());
}
+ private void handleCurrentUserChanged(@UserIdInt int userId) {
+ mCurrentUserId = userId;
+ }
+
/**
* Callback interface implemented by the Window Manager.
*/
@@ -3150,6 +3157,9 @@ public class InputManagerService extends IInputManager.Stub
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ case MSG_CURRENT_USER_CHANGED:
+ handleCurrentUserChanged((int) msg.obj);
+ break;
}
}
}
@@ -3513,6 +3523,11 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
+ public void setCurrentUser(@UserIdInt int newUserId) {
+ mHandler.obtainMessage(MSG_CURRENT_USER_CHANGED, newUserId).sendToTarget();
+ }
+
+ @Override
public boolean setKernelWakeEnabled(int deviceId, boolean enabled) {
return mNative.setKernelWakeEnabled(deviceId, enabled);
}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index bd1625ea6d3e..4d93e6570a79 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -1012,16 +1012,16 @@ final class KeyGestureController {
if (device == null) {
return;
}
+ KeyGestureEvent keyGestureEvent = new KeyGestureEvent(event);
if (event.action == KeyGestureEvent.ACTION_GESTURE_COMPLETE) {
KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, event.keycodes,
- event.modifierState,
- KeyGestureEvent.keyGestureTypeToLogEvent(event.gestureType));
+ event.modifierState, keyGestureEvent.getLogEvent());
}
notifyAllListeners(event);
while (mLastHandledEvents.size() >= MAX_TRACKED_EVENTS) {
mLastHandledEvents.removeFirst();
}
- mLastHandledEvents.addLast(new KeyGestureEvent(event));
+ mLastHandledEvents.addLast(keyGestureEvent);
}
@MainThread
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 48cc03221124..abf3da45d0cb 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -49,10 +49,6 @@ import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
-import static android.app.NotificationChannel.NEWS_ID;
-import static android.app.NotificationChannel.PROMOTIONS_ID;
-import static android.app.NotificationChannel.RECS_ID;
-import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
@@ -108,9 +104,7 @@ import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
-import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
-import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.notificationClassification;
import static android.service.notification.Flags.notificationForceGrouping;
@@ -6924,21 +6918,19 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
@Nullable
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
private NotificationChannel getClassificationChannelLocked(NotificationRecord r,
Bundle adjustments) {
int type = adjustments.getInt(KEY_TYPE);
- if (TYPE_NEWS == type) {
- return mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
- } else if (TYPE_PROMOTION == type) {
- return mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
- } else if (TYPE_SOCIAL_MEDIA == type) {
- return mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
- } else if (TYPE_CONTENT_RECOMMENDATION == type) {
- return mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+ if (type >= TYPE_PROMOTION && type <= TYPE_CONTENT_RECOMMENDATION) {
+ NotificationChannel channel = mPreferencesHelper.getReservedChannel(
+ r.getSbn().getPackageName(), r.getUid(), type);
+ if (channel == null) {
+ channel = mPreferencesHelper.createReservedChannel(
+ r.getSbn().getPackageName(), r.getUid(), type);
+ handleSavePolicyFile();
+ }
+ return channel;
}
return null;
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 964a5d002bf2..d26a5aa5491f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
import static android.app.NotificationChannel.PROMOTIONS_ID;
@@ -32,6 +33,10 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.notificationClassification;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
@@ -66,6 +71,7 @@ import android.os.Process;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.provider.Settings;
+import android.service.notification.Adjustment;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
import android.service.notification.RankingHelperProto;
@@ -549,10 +555,6 @@ public class PreferencesHelper implements RankingConfig {
Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
}
- if (notificationClassification()) {
- addReservedChannelsLocked(r);
- }
-
if (r.uid == UNKNOWN_UID) {
if (Flags.persistIncompleteRestoreData()) {
r.userId = userId;
@@ -587,7 +589,7 @@ public class PreferencesHelper implements RankingConfig {
private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws
PackageManager.NameNotFoundException {
- if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ if (!r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
// Not present
return false;
}
@@ -598,7 +600,7 @@ public class PreferencesHelper implements RankingConfig {
}
// Remove Default Channel.
- r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+ r.channels.remove(DEFAULT_CHANNEL_ID);
return true;
}
@@ -609,8 +611,8 @@ public class PreferencesHelper implements RankingConfig {
return false;
}
- if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
+ if (r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
+ r.channels.get(DEFAULT_CHANNEL_ID).setName(mContext.getString(
com.android.internal.R.string.default_notification_channel_label));
return false;
}
@@ -623,7 +625,7 @@ public class PreferencesHelper implements RankingConfig {
// Create Default Channel
NotificationChannel channel;
channel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID,
+ DEFAULT_CHANNEL_ID,
mContext.getString(R.string.default_notification_channel_label),
r.importance);
channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
@@ -642,38 +644,25 @@ public class PreferencesHelper implements RankingConfig {
return true;
}
- private void addReservedChannelsLocked(PackagePreferences p) {
- if (!p.channels.containsKey(NotificationChannel.PROMOTIONS_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.PROMOTIONS_ID,
- mContext.getString(R.string.promotional_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
- }
-
- if (!p.channels.containsKey(NotificationChannel.RECS_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.RECS_ID,
- mContext.getString(R.string.recs_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
- }
-
- if (!p.channels.containsKey(NotificationChannel.NEWS_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.NEWS_ID,
- mContext.getString(R.string.news_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
- }
-
- if (!p.channels.containsKey(NotificationChannel.SOCIAL_MEDIA_ID)) {
- NotificationChannel channel = new NotificationChannel(
- NotificationChannel.SOCIAL_MEDIA_ID,
- mContext.getString(R.string.social_notification_channel_label),
- IMPORTANCE_LOW);
- p.channels.put(channel.getId(), channel);
+ private NotificationChannel addReservedChannelLocked(PackagePreferences p, String channelId) {
+ String label = "";
+ switch (channelId) {
+ case PROMOTIONS_ID:
+ label = mContext.getString(R.string.promotional_notification_channel_label);
+ break;
+ case RECS_ID:
+ label = mContext.getString(R.string.recs_notification_channel_label);
+ break;
+ case NEWS_ID:
+ label = mContext.getString(R.string.news_notification_channel_label);
+ break;
+ case SOCIAL_MEDIA_ID:
+ label = mContext.getString(R.string.social_notification_channel_label);
+ break;
}
+ NotificationChannel channel = new NotificationChannel(channelId, label, IMPORTANCE_LOW);
+ p.channels.put(channelId, channel);
+ return channel;
}
public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
@@ -1078,7 +1067,7 @@ public class PreferencesHelper implements RankingConfig {
if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
}
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
+ if (DEFAULT_CHANNEL_ID.equals(channel.getId())) {
throw new IllegalArgumentException("Reserved id");
}
// Only the user can update bundle channel settings
@@ -1411,6 +1400,54 @@ public class PreferencesHelper implements RankingConfig {
}
}
+ private @Nullable String getChannelIdForBundleType(@Adjustment.Types int type) {
+ switch (type) {
+ case TYPE_CONTENT_RECOMMENDATION:
+ return RECS_ID;
+ case TYPE_NEWS:
+ return NEWS_ID;
+ case TYPE_PROMOTION:
+ return PROMOTIONS_ID;
+ case TYPE_SOCIAL_MEDIA:
+ return SOCIAL_MEDIA_ID;
+ }
+ return null;
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public NotificationChannel getReservedChannel(String pkg, int uid,
+ @Adjustment.Types int type) {
+ if (!notificationClassification()) {
+ return null;
+ }
+ Objects.requireNonNull(pkg);
+ String channelId = getChannelIdForBundleType(type);
+ if (channelId == null) {
+ return null;
+ }
+ NotificationChannel channel =
+ getConversationNotificationChannel(pkg, uid, channelId, null, true, false);
+ return channel;
+ }
+
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public NotificationChannel createReservedChannel(String pkg, int uid,
+ @Adjustment.Types int type) {
+ if (!notificationClassification()) {
+ return null;
+ }
+ Objects.requireNonNull(pkg);
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+ if (r == null) {
+ return null;
+ }
+ String channelId = getChannelIdForBundleType(type);
+ if (channelId == null) {
+ return null;
+ }
+ return addReservedChannelLocked(r, channelId);
+ }
+
@Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
@@ -1429,7 +1466,7 @@ public class PreferencesHelper implements RankingConfig {
return null;
}
if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+ channelId = DEFAULT_CHANNEL_ID;
}
NotificationChannel channel = null;
if (conversationId != null) {
@@ -1540,7 +1577,7 @@ public class PreferencesHelper implements RankingConfig {
int N = r.channels.size() - 1;
for (int i = N; i >= 0; i--) {
String key = r.channels.keyAt(i);
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+ if (!DEFAULT_CHANNEL_ID.equals(key)) {
r.channels.remove(key);
}
}
@@ -1658,10 +1695,7 @@ public class PreferencesHelper implements RankingConfig {
&& (activeChannelFilter == null
|| (includeBlocked && nc.getImportance() == IMPORTANCE_NONE)
|| activeChannelFilter.contains(nc.getId()))
- && !PROMOTIONS_ID.equals(nc.getId())
- && !NEWS_ID.equals(nc.getId())
- && !SOCIAL_MEDIA_ID.equals(nc.getId())
- && !RECS_ID.equals(nc.getId());
+ && !SYSTEM_RESERVED_IDS.contains(nc.getId());
if (includeChannel) {
if (nc.getGroup() != null) {
if (r.groups.get(nc.getGroup()) != null) {
@@ -1924,9 +1958,23 @@ public class PreferencesHelper implements RankingConfig {
public boolean onlyHasDefaultChannel(String pkg, int uid) {
synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
- if (r.channels.size() == (notificationClassification() ? 5 : 1)
- && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- return true;
+ if (r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
+ if (r.channels.size() == 1) {
+ return true;
+ }
+ if (notificationClassification()) {
+ if (r.channels.size() <= 5) {
+ for (NotificationChannel c : r.channels.values()) {
+ if (!SYSTEM_RESERVED_IDS.contains(c.getId()) &&
+ !DEFAULT_CHANNEL_ID.equals(c.getId())) {
+ return false;
+ }
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
}
return false;
}
@@ -2744,9 +2792,9 @@ public class PreferencesHelper implements RankingConfig {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
if (PackagePreferences.channels.containsKey(
- NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ DEFAULT_CHANNEL_ID)) {
PackagePreferences.channels.get(
- NotificationChannel.DEFAULT_CHANNEL_ID).setName(
+ DEFAULT_CHANNEL_ID).setName(
context.getResources().getString(
R.string.default_notification_channel_label));
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 512b19582237..6ea5369040fe 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -4445,9 +4445,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
* @return the uid of the owner this session
*/
public int getInstallerUid() {
- synchronized (mLock) {
- return mInstallerUid;
- }
+ return mInstallerUid;
}
/**
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 66ec53e6500e..a1236e533beb 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -31,6 +31,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
+import android.hardware.input.AppLaunchData;
import android.hardware.input.KeyGestureEvent;
import android.os.Handler;
import android.os.RemoteException;
@@ -48,6 +49,7 @@ import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
import com.android.internal.util.XmlUtils;
@@ -63,6 +65,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -131,7 +134,10 @@ public class ModifierShortcutManager {
private boolean mConsumeSearchKeyUp = true;
private UserHandle mCurrentUser;
private final Map<Pair<Character, Boolean>, Bookmark> mBookmarks = new HashMap<>();
+ @GuardedBy("mAppIntentCache")
+ private final Map<AppLaunchData, Intent> mAppIntentCache = new HashMap<>();
+ @SuppressLint("MissingPermission")
ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
mContext = context;
mHandler = handler;
@@ -146,6 +152,17 @@ public class ModifierShortcutManager {
} else {
mRoleIntents.remove(roleName);
}
+ synchronized (mAppIntentCache) {
+ mAppIntentCache.entrySet().removeIf(
+ entry -> {
+ if (entry.getKey() instanceof AppLaunchData.RoleData) {
+ return Objects.equals(
+ ((AppLaunchData.RoleData) entry.getKey()).getRole(),
+ roleName);
+ }
+ return false;
+ });
+ }
}, UserHandle.ALL);
mCurrentUser = currentUser;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -159,6 +176,10 @@ public class ModifierShortcutManager {
// so clear the cache.
clearRoleIntents();
clearComponentIntents();
+
+ synchronized (mAppIntentCache) {
+ mAppIntentCache.clear();
+ }
}
void clearRoleIntents() {
@@ -748,6 +769,46 @@ public class ModifierShortcutManager {
shortcuts);
}
+ private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) {
+ Context context = mContext.createContextAsUser(mCurrentUser, 0);
+ synchronized (mAppIntentCache) {
+ Intent intent = mAppIntentCache.get(data);
+ if (intent != null) {
+ return intent;
+ }
+ if (data instanceof AppLaunchData.CategoryData) {
+ intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+ ((AppLaunchData.CategoryData) data).getCategory());
+ } else if (data instanceof AppLaunchData.RoleData) {
+ intent = getRoleLaunchIntent(context, ((AppLaunchData.RoleData) data).getRole());
+ } else if (data instanceof AppLaunchData.ComponentData) {
+ AppLaunchData.ComponentData componentData = (AppLaunchData.ComponentData) data;
+ intent = resolveComponentNameIntent(context, componentData.getPackageName(),
+ componentData.getClassName());
+ }
+ if (intent != null) {
+ mAppIntentCache.put(data, intent);
+ }
+ return intent;
+ }
+ }
+
+ boolean launchApplication(@NonNull AppLaunchData data) {
+ Intent intent = getIntentFromAppLaunchData(data);
+ if (intent == null) {
+ return false;
+ }
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivityAsUser(intent, mCurrentUser);
+ return true;
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Not launching app because "
+ + "the activity to which it refers to was not found: " + data);
+ }
+ return false;
+ }
+
/**
* Given an intent to launch an application and the character and shift state that should
* trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and
@@ -869,7 +930,7 @@ public class ModifierShortcutManager {
// TODO(b/280423320): Add new field package name associated in the
// KeyboardShortcutEvent atom and log it accordingly.
- return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+ return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION;
}
@KeyGestureEvent.KeyGestureType
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6a7f22e7b06c..3ab1009b22e1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -149,6 +149,7 @@ import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.input.AppLaunchData;
import android.hardware.input.InputManager;
import android.hardware.input.KeyGestureEvent;
import android.media.AudioManager;
@@ -4037,6 +4038,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
@@ -4279,6 +4281,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
+ AppLaunchData data = event.getAppLaunchData();
+ if (complete && isUserSetupComplete() && !keyguardOn
+ && data != null && mModifierShortcutManager.launchApplication(data)) {
+ dismissKeyboardShortcutsMenu();
+ return true;
+ }
+ break;
}
return false;
}
@@ -6948,6 +6958,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (modifierShortcutManagerMultiuser()) {
mModifierShortcutManager.setCurrentUser(UserHandle.of(newUserId));
}
+ mInputManagerInternal.setCurrentUser(newUserId);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8dab71701d18..2070c91eaccd 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1275,8 +1275,8 @@ class ActivityStarter {
"Creator PermissionPolicyService.checkStartActivity Caused abortion.",
intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
}
- intent.removeCreatorTokenInfo();
}
+ intent.removeCreatorToken();
// Merge the two options bundles, while realCallerOptions takes precedence.
ActivityOptions checkedOptions = options != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 111e74e9b590..25a1ea9579e7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1228,7 +1228,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
Bundle bOptions) {
- mAmInternal.addCreatorToken(intent, callingPackage);
return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 3b81f0a6191e..4c1ac39a5da0 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -178,4 +178,675 @@
column="51"/>
</issue>
-</issues> \ No newline at end of file
+ <issue
+ id="MissingPermissionAnnotation"
+ message="onShellCommand should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java"
+ line="128"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="monitorState should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="95"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="makeVisible should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="100"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="makeInvisible should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="105"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="enable should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="110"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="disable should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+ line="115"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="getTimeoutTime should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="430"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="extendTimeRemaining should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="443"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="setVerificationPolicy should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="456"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="reportVerificationIncomplete should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="470"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="reportVerificationComplete should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="486"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="MissingPermissionAnnotation"
+ message="reportVerificationCompleteWithExtensionResponse should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+ errorLine1=" @Override"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+ line="492"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mService.mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+ line="592"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+ line="1636"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+ line="1654"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="1820"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="1875"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="1980"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(REMOVE_TASKS, &quot;removeTask()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2116"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(REMOVE_TASKS, &quot;removeAllVisibleRecentTasks()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2144"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.FORCE_BACK,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2206"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2228"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="2371"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, &quot;moveRootTaskToDisplay()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3103"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3157"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, &quot;unlock keyguard&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3640"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3683"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3701"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, &quot;updateConfiguration()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3904"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, &quot;getTaskSnapshot()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="3978"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, &quot;takeTaskSnapshot()&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4000"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4029"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4057"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4074"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, &quot;stopAppSwitches&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4136"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, &quot;resumeAppSwitches&quot;);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4147"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4198"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4215"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="4280"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="5836"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,"
+ errorLine2=" ^">
+ <location
+ file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+ line="5849"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IColorDisplayManager permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" getContext().enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/display/color/ColorDisplayService.java"
+ line="2152"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+ line="779"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+ line="820"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+ line="906"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="INotificationManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (PERMISSION_GRANTED"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java"
+ line="6691"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="INotificationManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (PERMISSION_GRANTED"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java"
+ line="6712"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IOnDeviceIntelligenceManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java"
+ line="237"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageInstaller permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java"
+ line="1881"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageInstaller permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java"
+ line="1892"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingPermission(Manifest.permission.SEND_DEVICE_CUSTOMIZATION_READY,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
+ line="5798"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPackageManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
+ line="6266"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="1406"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="1427"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="1734"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="2509"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="2564"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" if (mContext.checkCallingPermission(android.Manifest.permission.ACCESS_TUNED_INFO)"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+ line="2932"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IUriGrantsManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java"
+ line="366"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IUriGrantsManager permission check can be converted to @EnforcePermission annotation"
+ errorLine1=" mAmInternal.enforceCallingPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java"
+ line="444"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IVcnManagementService permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(DUMP, TAG);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/services/core/java/com/android/server/VcnManagementService.java"
+ line="1329"
+ column="9"/>
+ </issue>
+
+</issues>
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6baab25d0dce..ed56382450e9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1399,10 +1399,6 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(BatteryService.class);
t.traceEnd();
- t.traceBegin("StartTradeInModeService");
- mSystemServiceManager.startService(TradeInModeService.class);
- t.traceEnd();
-
// Tracks application usage stats.
t.traceBegin("StartUsageService");
mSystemServiceManager.startService(UsageStatsService.class);
@@ -1772,6 +1768,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(AdvancedProtectionService.Lifecycle.class);
t.traceEnd();
}
+
+ if (!isWatch && !isTv && !isAutomotive) {
+ t.traceBegin("StartTradeInModeService");
+ mSystemServiceManager.startService(TradeInModeService.class);
+ t.traceEnd();
+ }
+
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 105147fe212d..42c171bb4351 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.camera2.CameraManager;
+import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
@@ -67,6 +68,8 @@ public final class ProfcollectForwardingService extends SystemService {
private int mUsageSetting;
private boolean mUploadEnabled;
+ private boolean mAdbActive;
+
private IProfCollectd mIProfcollect;
private static ProfcollectForwardingService sSelfService;
private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
@@ -84,6 +87,14 @@ public final class ProfcollectForwardingService extends SystemService {
Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
createAndUploadReport(sSelfService);
}
+ if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
+ boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+ boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
+ if (isADB) {
+ Log.d(LOG_TAG, "Received broadcast that ADB became " + connected);
+ mAdbActive = connected;
+ }
+ }
}
};
@@ -106,8 +117,12 @@ public final class ProfcollectForwardingService extends SystemService {
mUploadEnabled =
context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
+ // TODO: ADB might already be active when our service started.
+ mAdbActive = false;
+
final IntentFilter filter = new IntentFilter();
filter.addAction(INTENT_UPLOAD_PROFILES);
+ filter.addAction(UsbManager.ACTION_USB_STATE);
context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
}
@@ -281,6 +296,9 @@ public final class ProfcollectForwardingService extends SystemService {
if (mIProfcollect == null) {
return;
}
+ if (mAdbActive) {
+ return;
+ }
if (Utils.withFrequency("applaunch_trace_freq", 5)) {
Utils.traceSystem(mIProfcollect, "applaunch");
}
@@ -303,6 +321,9 @@ public final class ProfcollectForwardingService extends SystemService {
if (mIProfcollect == null) {
return;
}
+ if (mAdbActive) {
+ return;
+ }
if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
// Dex2oat could take a while before it starts. Add a short delay before start tracing.
Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e845d80b412a..dec7f09f2da4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -45,6 +45,9 @@ import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -98,7 +101,10 @@ import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Condition.SOURCE_CONTEXT;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_TRUE;
@@ -16767,6 +16773,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
r.applyAdjustments();
assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+
+ signals.putInt(KEY_TYPE, TYPE_PROMOTION);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ assertThat(r.getChannel().getId()).isEqualTo(PROMOTIONS_ID);
+
+ signals.putInt(KEY_TYPE, TYPE_SOCIAL_MEDIA);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ assertThat(r.getChannel().getId()).isEqualTo(SOCIAL_MEDIA_ID);
+
+ signals.putInt(KEY_TYPE, TYPE_CONTENT_RECOMMENDATION);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ assertThat(r.getChannel().getId()).isEqualTo(RECS_ID);
}
@Test
@@ -17066,7 +17090,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testAppCannotUseReservedBundleChannels() throws Exception {
- mBinderService.getBubblePreferenceForPackage(mPkg, mUid);
+ mService.mPreferencesHelper.createReservedChannel(mPkg, mUid, TYPE_NEWS);
NotificationChannel news = mBinderService.getNotificationChannel(
mPkg, mContext.getUserId(), mPkg, NEWS_ID);
assertThat(news).isNotNull();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 36fa1b82fe69..1a1da0f6d408 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -54,6 +54,11 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_OTHER;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
import static android.service.notification.Flags.notificationClassification;
@@ -640,6 +645,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel updateNews = null;
if (notificationClassification()) {
+ mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_NEWS);
// change one of the reserved bundle channels to ensure changes are persisted across
// boot
updateNews = mHelper.getNotificationChannel(
@@ -1210,22 +1216,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\" uid=\"10002\">\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
+ "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1233,10 +1226,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "=\"false\" uid=\"10001\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1246,15 +1235,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1321,22 +1301,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\">\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
// Importance default because on in permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" "
@@ -1345,10 +1312,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1358,15 +1321,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1433,22 +1387,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\">\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
// Importance 0 because missing from permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"0\" show_badge=\"true\" "
@@ -1457,10 +1398,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.social\" "
- + "name=\"Social\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1470,15 +1407,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
- + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
- + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
- + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
- + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -2183,10 +2111,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
+ assertEquals(Notification.PRIORITY_DEFAULT,
+ mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
assertEquals(VISIBILITY_NO_OVERRIDE,
mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
@@ -2549,7 +2477,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
List<NotificationChannel> channels =
mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true).getList();
// Default channel + non-deleted channel + system defaults
- assertEquals(notificationClassification() ? 6 : 2, channels.size());
+ assertEquals(2, channels.size());
for (NotificationChannel nc : channels) {
if (channel2.getId().equals(nc.getId())) {
compareChannels(channel2, nc);
@@ -2559,7 +2487,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// Returns deleted channels too
channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true).getList();
// Includes system channel(s)
- assertEquals(notificationClassification() ? 7 : 3, channels.size());
+ assertEquals(3, channels.size());
for (NotificationChannel nc : channels) {
if (channel2.getId().equals(nc.getId())) {
compareChannels(channelMap.get(nc.getId()), nc);
@@ -3036,7 +2964,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testOnlyHasDefaultChannel() throws Exception {
assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
@@ -3047,6 +2974,18 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testOnlyHasDefaultChannel_bundleExists() throws Exception {
+ mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_NEWS);
+ assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
+ assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
+
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false,
+ UID_N_MR1, false);
+ assertFalse(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
+ }
+
+ @Test
public void testCreateDeletedChannel() throws Exception {
long[] vibration = new long[]{100, 67, 145, 156};
NotificationChannel channel =
@@ -3315,7 +3254,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// user 0 records remain
for (int i = 0; i < user0Uids.length; i++) {
- assertEquals(notificationClassification() ? 5 : 1,
+ assertEquals(1,
mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true)
.getList().size());
}
@@ -3346,7 +3285,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM,
new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
- assertEquals(notificationClassification() ? 6 : 2,
+ assertEquals(2,
mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true)
.getList().size());
}
@@ -3420,7 +3359,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testRecordDefaults() throws Exception {
assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertEquals(notificationClassification() ? 5 : 1,
+ assertEquals(1,
mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true)
.getList().size());
}
@@ -3659,9 +3598,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false,
UID_N_MR1, false);
}
- if (notificationClassification()) {
- numChannels += 4;
- }
expectedChannels.put(pkgName, numChannels);
}
@@ -4883,10 +4819,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testTooManyChannels() {
int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
- if (notificationClassification()) {
- // reserved channels lower limit
- numToCreate -= 4;
- }
for (int i = 0; i < numToCreate; i++) {
NotificationChannel channel = new NotificationChannel(String.valueOf(i),
String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
@@ -4907,10 +4839,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testTooManyChannels_xml() throws Exception {
int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
- if (notificationClassification()) {
- // reserved channels lower limit
- numToCreate -= 4;
- }
String extraChannel = "EXTRA";
String extraChannel1 = "EXTRA1";
@@ -5928,9 +5856,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("expected number of events",
- notificationClassification() ? 7 : 3,
- events.size());
+ assertEquals("expected number of events", 3, events.size());
for (StatsEvent ev : events) {
// all of these events should be of PackageNotificationChannelPreferences type,
// and therefore we expect the atom to have this field.
@@ -5971,17 +5897,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_O, UID_O, channelC, true, false, UID_O, false);
List<String> channels = new LinkedList<>(Arrays.asList("a", "b", "c"));
- if (notificationClassification()) {
- channels.add(NEWS_ID);
- channels.add(PROMOTIONS_ID);
- channels.add(SOCIAL_MEDIA_ID);
- channels.add(RECS_ID);
- }
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events", notificationClassification() ? 7 : 3, events.size());
+ assertEquals("total events", 3, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6015,7 +5935,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.pullPackageChannelPreferencesStats(events);
// In this case, we want to check the properties of the conversation channel (not parent)
- assertEquals("total events", notificationClassification() ? 6 : 2, events.size());
+ assertEquals("total events", 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6047,9 +5967,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events",
- notificationClassification() ? 6 : 2,
- events.size());
+ assertEquals("total events", 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6080,9 +5998,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events",
- notificationClassification() ? 6 : 2,
- events.size());
+ assertEquals("total events", 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6367,8 +6283,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testGetNotificationChannels_omitBundleChannels() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, false).getList()).isEmpty();
}
@@ -6376,18 +6291,34 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testNotificationBundles() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
-
- // verify 4 reserved channels are created
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance())
.isEqualTo(IMPORTANCE_LOW);
- assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
- .getImportance()).isEqualTo(IMPORTANCE_LOW);
- assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false)
- .getImportance()).isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(1);
+
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_SOCIAL_MEDIA);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false).
+ getImportance()).isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(2);
+
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_CONTENT_RECOMMENDATION);
assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance())
.isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(3);
+
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_PROMOTION);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
+ .getImportance()).isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(4);
+
+ // only the first 4 types are created; no others
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_OTHER);
+ assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+ .isEqualTo(4);
}
@Test
@@ -6417,8 +6348,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testNotificationBundles_appsCannotUpdate() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
NotificationChannel fromApp =
new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
@@ -6431,8 +6361,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testNotificationBundles_osCanAllowToBypassDnd() {
- // do something that triggers settings creation for an app
- mHelper.setShowBadge(PKG_O, UID_O, true);
+ mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
NotificationChannel fromApp =
new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
@@ -6442,18 +6371,17 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception {
- mHelper.setShowBadge(PKG_P, UID_P, true);
// the public create/update methods should prevent this, so take advantage of the fact that
// the object is in the same process
- mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true);
+ mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_SOCIAL_MEDIA).setDeleted(true);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
UserHandle.USER_ALL, SOCIAL_MEDIA_ID);
loadStreamXml(baos, false, UserHandle.USER_ALL);
- assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).
- isDeleted()).isFalse();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, SOCIAL_MEDIA_ID, true)
+ .isDeleted()).isFalse();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index c186a0355588..28ae271e20fc 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -43,6 +43,8 @@ import static android.view.KeyEvent.KEYCODE_Z;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.KeyGestureEvent;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -85,7 +87,8 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* Test meta+ shortcuts defined in bookmarks.xml.
*/
@Test
- public void testMetaShortcuts() {
+ @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testMetaShortcuts_withoutKeyGestureEventHandling() {
for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
final int keyCode = INTENT_SHORTCUTS.keyAt(i);
final String category = INTENT_SHORTCUTS.valueAt(i);
@@ -115,6 +118,49 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
}
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ public void testMetaShortcuts_withKeyGestureEventHandling() {
+ for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
+ final String category = INTENT_SHORTCUTS.valueAt(i);
+ mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(category))
+ .build()
+ );
+ mPhoneWindowManager.assertLaunchCategory(category);
+ }
+
+ mPhoneWindowManager.overrideRoleManager();
+ for (int i = 0; i < ROLE_SHORTCUTS.size(); i++) {
+ final String role = ROLE_SHORTCUTS.valueAt(i);
+
+ mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForRole(role))
+ .build()
+ );
+ mPhoneWindowManager.assertLaunchRole(role);
+ }
+
+ mPhoneWindowManager.sendKeyGestureEvent(
+ new KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .setAppLaunchData(
+ new AppLaunchData.ComponentData("com.test",
+ "com.test.BookmarkTest"))
+ .build()
+ );
+ mPhoneWindowManager.assertActivityTargetLaunched(
+ new ComponentName("com.test", "com.test.BookmarkTest"));
+
+ }
+
/**
* ALT + TAB to show recent apps.
*/