summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp8
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/android/app/Notification.java24
-rw-r--r--core/java/android/app/NotificationManager.java11
-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.aconfig7
-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/os/Build.java12
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig1
-rw-r--r--core/java/android/window/BackEvent.java5
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/com/android/internal/os/ApplicationSharedMemory.java31
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressBar.java2
-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/tests/coretests/Android.bp4
-rw-r--r--core/tests/coretests/AndroidTest.xml12
-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.java30
-rw-r--r--libs/WindowManager/Shell/Android.bp7
-rw-r--r--libs/WindowManager/Shell/multivalentTests/Android.bp2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt31
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt22
-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/draganddrop/DragUtils.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/OWNERS5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java23
-rw-r--r--lint-baseline.xml13
-rw-r--r--media/java/android/media/AudioDeviceInfo.java2
-rw-r--r--media/java/android/media/AudioManager.java34
-rw-r--r--media/java/android/media/audio/common/AidlConversion.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java1
-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/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/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt10
-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/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt82
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt5
-rw-r--r--packages/SystemUI/res/drawable/volume_background_top.xml41
-rw-r--r--packages/SystemUI/res/drawable/volume_background_top_legacy.xml27
-rw-r--r--packages/SystemUI/res/drawable/volume_dialog_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml16
-rw-r--r--packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml24
-rw-r--r--packages/SystemUI/res/drawable/volume_ringer_item_bg.xml22
-rw-r--r--packages/SystemUI/res/layout-land/volume_dialog_legacy.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml5
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_legacy.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_drawer.xml108
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml135
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt6
-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/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/statusbar/notification/ConversationNotifications.kt3
-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/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/AmbientState.java2
-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.java167
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt169
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt3
-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/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt6
-rw-r--r--services/core/java/com/android/server/SystemConfig.java21
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/display/DisplayTopology.java250
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java8
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java51
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java7
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java1
-rw-r--r--services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java17
-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/integrity/parser/RuleIndexRange.java3
-rw-r--r--services/core/java/com/android/server/integrity/parser/RuleIndexingController.java128
-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/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/java/com/android/server/wm/DragDropController.java2
-rw-r--r--services/core/lint-baseline.xml673
-rw-r--r--services/java/com/android/server/SystemServer.java11
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt292
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java279
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java216
-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
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java12
154 files changed, 5331 insertions, 1532 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c7750123aef0..db90185fed61 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -349,6 +349,7 @@ aconfig_declarations {
name: "android.security.flags-aconfig",
package: "android.security",
container: "system",
+ exportable: true,
srcs: ["core/java/android/security/*.aconfig"],
}
@@ -359,6 +360,13 @@ java_aconfig_library {
}
java_aconfig_library {
+ name: "android.security.flags-aconfig-java-export",
+ aconfig_declarations: "android.security.flags-aconfig",
+ mode: "exported",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
name: "android.security.flags-aconfig-java-host",
aconfig_declarations: "android.security.flags-aconfig",
host_supported: true,
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/api/system-current.txt b/core/api/system-current.txt
index 389789b5fff4..d1a9e67d25ee 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7340,6 +7340,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public void setVolumeGroupVolumeIndex(int, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setWiredDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -7349,6 +7350,8 @@ package android.media {
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int DEVICE_CONNECTION_STATE_CONNECTED = 1; // 0x1
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int DEVICE_CONNECTION_STATE_DISCONNECTED = 0; // 0x0
field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5
field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 64aa705447aa..ca1662e6bfd0 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11823,28 +11823,42 @@ public class Notification implements Parcelable
sanitizeProgressColor(indeterminateColor,
backgroundColor, defaultProgressColor));
} else {
-
// Ensure segment color contrasts.
final List<Segment> segments = new ArrayList<>();
+ int totalLength = 0;
for (Segment segment : mProgressSegments) {
- segments.add(sanitizeSegment(segment, backgroundColor,
- defaultProgressColor));
+ final int length = segment.getLength();
+ if (length <= 0) continue;
+
+ try {
+ totalLength += Math.addExact(totalLength, length);
+ segments.add(sanitizeSegment(segment, backgroundColor,
+ defaultProgressColor));
+ } catch (ArithmeticException e) {
+ totalLength = DEFAULT_PROGRESS_MAX;
+ segments.clear();
+ break;
+ }
}
// Create default segment when no segments are provided.
if (segments.isEmpty()) {
- segments.add(sanitizeSegment(new Segment(100), backgroundColor,
+ totalLength = DEFAULT_PROGRESS_MAX;
+ segments.add(sanitizeSegment(new Segment(totalLength), backgroundColor,
defaultProgressColor));
}
// Ensure point color contrasts.
final List<Point> points = new ArrayList<>();
for (Point point : mProgressPoints) {
+ final int position = point.getPosition();
+ if (position < 0 || position > totalLength) continue;
+
points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
}
model = new NotificationProgressModel(segments, points,
- mProgress, mIsStyledByProgress);
+ Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
}
return model;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 06bf67c1e86f..f2a36e90c8ce 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.service.notification.Flags.notificationClassification;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -752,6 +754,11 @@ public class NotificationManager {
INotificationManager service = getService();
String pkg = mContext.getPackageName();
+ if (notificationClassification()
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(notification.getChannelId())) {
+ return;
+ }
+
try {
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
@@ -1131,6 +1138,10 @@ public class NotificationManager {
* had before it was deleted.
*/
public void deleteNotificationChannel(String channelId) {
+ if (notificationClassification()
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(channelId)) {
+ return;
+ }
INotificationManager service = getService();
try {
service.deleteNotificationChannel(mContext.getPackageName(), channelId);
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 03377445123e..6f70586881be 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -320,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/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/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 9bb1039849e5..d50c7e5f1687 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -69,6 +69,7 @@ flag {
description: "Android Advanced Protection Mode Service and Manager"
bug: "352420507"
is_fixed_read_only: true
+ is_exported: true
}
flag {
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/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index b805f5a2734b..c4a9e5722369 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -362,3 +362,10 @@ flag {
description: "Change the default display's windowing mode to freeform when display connected in extended mode."
bug: "374849026"
}
+
+flag {
+ name: "enable_desktop_windowing_pip"
+ namespace: "lse_desktop_experience"
+ description: "Enables PiP features in desktop mode."
+ bug: "350475854"
+}
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 51a9cf67b06f..c5eac081c85d 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -453,7 +453,7 @@ public final class NotificationProgressBar extends ProgressBar {
}
for (ProgressStyle.Point point : points) {
final int pos = point.getPosition();
- if (pos <= 0 || pos >= progressMax) {
+ if (pos < 0 || pos > progressMax) {
throw new IllegalArgumentException("Invalid Point position : " + pos);
}
}
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/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/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 35765a9f8e08..5613caf4427e 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -172,36 +172,6 @@ public class NotificationProgressBarTest {
}
@Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_pointPositionIsZero() {
- List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(100));
- 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);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void processAndConvertToDrawableParts_pointPositionAtMax() {
- List<ProgressStyle.Segment> segments = new ArrayList<>();
- segments.add(new ProgressStyle.Segment(100));
- 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);
- }
-
- @Test(expected = IllegalArgumentException.class)
public void processAndConvertToDrawableParts_pointPositionAboveMax() {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100));
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/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
index ee0d5bbed324..41d1b5c15369 100644
--- a/libs/WindowManager/Shell/multivalentTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -53,6 +53,8 @@ android_robolectric_test {
"mockito-robolectric-prebuilt",
"mockito-kotlin2",
"truth",
+ "flag-junit-base",
+ "flag-junit",
],
auto_gen_config: true,
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
index 398fd554f030..4214e6ff9eaf 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -18,14 +18,19 @@ package com.android.wm.shell.bubbles
import android.content.ComponentName
import android.content.Context
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.wm.shell.Flags
import com.android.wm.shell.taskview.TaskView
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -36,6 +41,9 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class BubbleTaskViewTest {
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
private lateinit var bubbleTaskView: BubbleTaskView
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var taskView: TaskView
@@ -75,14 +83,33 @@ class BubbleTaskViewTest {
assertThat(actualComponentName).isEqualTo(componentName)
}
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
@Test
- fun cleanup_invalidTaskId_doesNotRemoveTask() {
+ fun cleanup_flagOff_invalidTaskId_doesNotRemoveTask() {
bubbleTaskView.cleanup()
verify(taskView, never()).removeTask()
}
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
+ @Test
+ fun cleanup_flagOn_invalidTaskId_removesTask() {
+ bubbleTaskView.cleanup()
+ verify(taskView).removeTask()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
+ @Test
+ fun cleanup_flagOff_validTaskId_removesTask() {
+ val componentName = ComponentName(context, "TestClass")
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ bubbleTaskView.cleanup()
+ verify(taskView).removeTask()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
@Test
- fun cleanup_validTaskId_removesTask() {
+ fun cleanup_flagOn_validTaskId_removesTask() {
val componentName = ComponentName(context, "TestClass")
bubbleTaskView.listener.onTaskCreated(123, componentName)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index 712cc7c475bc..776ea9226b06 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -25,6 +25,7 @@ import android.os.Handler
import android.os.UserManager
import android.view.IWindowManager
import android.view.WindowManager
+import android.widget.FrameLayout
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -56,7 +57,9 @@ import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/** Test inflating bubbles with [BubbleViewInfoTask]. */
@SmallTest
@@ -158,18 +161,23 @@ class BubbleViewInfoTaskTest {
mock<BubbleProperties>()
)
- val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- bubblePositioner,
- bubbleData,
- surfaceSynchronizer,
- FloatingContentCoordinator(),
- bubbleController,
- mainExecutor
- )
+ // TODO: (b/371829099) - when optional overflow is no longer flagged we can enable this
+ // again, something about the overflow being added uncovers an issue with Robolectric and
+ // bitmaps; this is switched to a mock to work around that (b/375513387).
+// val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
+// bubbleStackView = BubbleStackView(
+// context,
+// bubbleStackViewManager,
+// bubblePositioner,
+// bubbleData,
+// surfaceSynchronizer,
+// FloatingContentCoordinator(),
+// bubbleController,
+// mainExecutor
+// )
+ bubbleStackView = mock<BubbleStackView>()
+ whenever(bubbleStackView.generateLayoutParams(any()))
+ .thenReturn(FrameLayout.LayoutParams(1000, 1000))
expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 4607a8ec1210..f59fed906e2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -583,9 +583,8 @@ public class ShellTaskOrganizer extends TaskOrganizer {
}
final boolean windowModeChanged =
data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
- final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
- if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && visibilityChanged)) {
+ if (windowModeChanged
+ || hasFreeformConfigurationChanged(data.getTaskInfo(), taskInfo)) {
mRecentTasks.ifPresent(recentTasks ->
recentTasks.onTaskRunningInfoChanged(taskInfo));
}
@@ -606,6 +605,17 @@ public class ShellTaskOrganizer extends TaskOrganizer {
}
}
+ private boolean hasFreeformConfigurationChanged(RunningTaskInfo oldTaskInfo,
+ RunningTaskInfo newTaskInfo) {
+ if (newTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+ return oldTaskInfo.isVisible != newTaskInfo.isVisible
+ || !oldTaskInfo.positionInParent.equals(newTaskInfo.positionInParent)
+ || !Objects.equals(oldTaskInfo.configuration.windowConfiguration.getAppBounds(),
+ newTaskInfo.configuration.windowConfiguration.getAppBounds());
+ }
+
@Override
public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
index 65f8e48eb822..68fc0c99abee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -16,14 +16,11 @@
package com.android.wm.shell.bubbles
-import android.app.ActivityTaskManager
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.content.ComponentName
-import android.os.RemoteException
-import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.Flags
import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import java.util.concurrent.Executor
/**
@@ -89,21 +86,8 @@ class BubbleTaskView(val taskView: TaskView, executor: Executor) {
* This should be called after all other cleanup animations have finished.
*/
fun cleanup() {
- if (taskId != INVALID_TASK_ID) {
- // Ensure the task is removed from WM
- if (ENABLE_SHELL_TRANSITIONS) {
- taskView.removeTask()
- } else {
- try {
- ActivityTaskManager.getService().removeTask(taskId)
- } catch (e: RemoteException) {
- Log.w(TAG, e.message ?: "")
- }
- }
+ if (Flags.enableTaskViewControllerCleanup() || taskId != INVALID_TASK_ID) {
+ taskView.removeTask()
}
}
-
- private companion object {
- const val TAG = "BubbleTaskView"
- }
}
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/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 22cfa328bfda..248a1124cd86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -125,25 +125,35 @@ public class DragUtils {
StringJoiner str = new StringJoiner("|");
if ((dragFlags & DRAG_FLAG_GLOBAL) != 0) {
str.add("GLOBAL");
- } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
str.add("GLOBAL_URI_READ");
- } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
str.add("GLOBAL_URI_WRITE");
- } else if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
str.add("GLOBAL_PERSISTABLE_URI_PERMISSION");
- } else if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
str.add("GLOBAL_PREFIX_URI_PERMISSION");
- } else if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
str.add("OPAQUE");
- } else if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
str.add("ACCESSIBILITY_ACTION");
- } else if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
str.add("REQUEST_SURFACE_FOR_RETURN_ANIMATION");
- } else if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
str.add("GLOBAL_SAME_APPLICATION");
- } else if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
str.add("START_INTENT_SENDER_ON_UNHANDLED_DRAG");
- } else if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+ }
+ if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
str.add("HIDE_CALLING_TASK_ON_DRAG_START");
}
return str.toString();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/OWNERS
new file mode 100644
index 000000000000..892d15079de6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/OWNERS
@@ -0,0 +1,5 @@
+# Bubbles team
+madym@google.com
+atsjenk@google.com
+liranb@google.com
+mpodolian@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index e74342e1910c..eaeedba8ef9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -40,6 +40,7 @@ import android.view.WindowInsets;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -525,8 +526,13 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
*/
void removeTask() {
if (mTaskToken == null) {
- // Call to remove task before we have one, do nothing
- Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+ if (Flags.enableTaskViewControllerCleanup()) {
+ // We don't have a task yet. Only clean up the controller
+ mTaskViewTransitions.removeTaskView(this);
+ } else {
+ // Call to remove task before we have one, do nothing
+ Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+ }
return;
}
mShellExecutor.execute(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 39648f65b4f3..ae75cb589de0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,11 +37,14 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
+import java.util.Map;
import java.util.Objects;
+import java.util.WeakHashMap;
/**
* Handles Shell Transitions that involve TaskView tasks.
@@ -49,8 +52,15 @@ import java.util.Objects;
public class TaskViewTransitions implements Transitions.TransitionHandler {
static final String TAG = "TaskViewTransitions";
- private final ArrayMap<TaskViewTaskController, TaskViewRequestedState> mTaskViews =
- new ArrayMap<>();
+ /**
+ * Map of {@link TaskViewTaskController} to {@link TaskViewRequestedState}.
+ * <p>
+ * {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and
+ * manages its lifecycle.
+ * Only keep a weak reference to the controller instance here to allow for it to be cleaned
+ * up when its TaskView is no longer used.
+ */
+ private final Map<TaskViewTaskController, TaskViewRequestedState> mTaskViews;
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
private final Transitions mTransitions;
private final boolean[] mRegistered = new boolean[]{false};
@@ -95,6 +105,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
public TaskViewTransitions(Transitions transitions) {
mTransitions = transitions;
+ if (Flags.enableTaskViewControllerCleanup()) {
+ mTaskViews = new WeakHashMap<>();
+ } else {
+ mTaskViews = new ArrayMap<>();
+ }
// Defer registration until the first TaskView because we want this to be the "first" in
// priority when handling requests.
// TODO(210041388): register here once we have an explicit ordering mechanism.
@@ -208,10 +223,21 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
- for (int i = 0; i < mTaskViews.size(); ++i) {
- if (mTaskViews.keyAt(i).getTaskInfo() == null) continue;
- if (taskInfo.token.equals(mTaskViews.keyAt(i).getTaskInfo().token)) {
- return mTaskViews.keyAt(i);
+ if (Flags.enableTaskViewControllerCleanup()) {
+ for (TaskViewTaskController controller : mTaskViews.keySet()) {
+ if (controller.getTaskInfo() == null) continue;
+ if (taskInfo.token.equals(controller.getTaskInfo().token)) {
+ return controller;
+ }
+ }
+ } else {
+ ArrayMap<TaskViewTaskController, TaskViewRequestedState> taskViews =
+ (ArrayMap<TaskViewTaskController, TaskViewRequestedState>) mTaskViews;
+ for (int i = 0; i < taskViews.size(); ++i) {
+ if (taskViews.keyAt(i).getTaskInfo() == null) continue;
+ if (taskInfo.token.equals(taskViews.keyAt(i).getTaskInfo().token)) {
+ return taskViews.keyAt(i);
+ }
}
}
return null;
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 049a5a0615e0..91be5f58b1f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -63,6 +63,7 @@ android_test {
"com.android.window.flags.window-aconfig-java",
"platform-test-annotations",
"flag-junit",
+ "platform-parametric-runner-lib",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f01ed84adc74..841b6cefcafa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -45,6 +45,8 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -589,9 +591,9 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
@Test
public void testRecentTasks_visibilityChanges_shouldNotifyTaskController() {
- RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
- RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
task2.isVisible = false;
mOrganizer.onTaskInfoChanged(task2);
@@ -600,6 +602,30 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
}
@Test
+ public void testRecentTasks_sizeChanges_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
+ task2.configuration.windowConfiguration.setAppBounds(new Rect(0, 0, 300, 400));
+
+ mOrganizer.onTaskInfoChanged(task2);
+
+ verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+ }
+
+ @Test
+ public void testRecentTasks_positionChanges_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
+ task2.positionInParent = new Point(200, 200);
+
+ mOrganizer.onTaskInfoChanged(task2);
+
+ verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+ }
+
+ @Test
public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
@@ -649,6 +675,13 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
return taskInfo;
}
+ private static RunningTaskInfo createFreeformTaskInfo(int taskId) {
+ RunningTaskInfo taskInfo = createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ taskInfo.positionInParent = new Point(100, 100);
+ taskInfo.configuration.windowConfiguration.setAppBounds(new Rect(0, 0, 200, 200));
+ return taskInfo;
+ }
+
private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener listener) {
final ArgumentCaptor<CompatUIInfo> capture = ArgumentCaptor.forClass(CompatUIInfo.class);
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index d3e40f21db23..60e203009e8c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -33,7 +33,8 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -42,10 +43,12 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,11 +57,23 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class TaskViewTransitionsTest extends ShellTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP);
+ }
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule;
+
@Mock
Transitions mTransitions;
@Mock
@@ -70,6 +85,10 @@ public class TaskViewTransitionsTest extends ShellTestCase {
TaskViewTransitions mTaskViewTransitions;
+ public TaskViewTransitionsTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(flags);
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
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/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 0efefb9d1bff..39b29d0156d2 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -279,6 +279,7 @@ public final class AudioDeviceInfo {
TYPE_AUX_LINE,
TYPE_IP,
TYPE_BUS,
+ TYPE_REMOTE_SUBMIX,
TYPE_HEARING_AID,
TYPE_BUILTIN_SPEAKER_SAFE,
TYPE_BLE_HEADSET,
@@ -312,6 +313,7 @@ public final class AudioDeviceInfo {
case TYPE_AUX_LINE:
case TYPE_IP:
case TYPE_BUS:
+ case TYPE_REMOTE_SUBMIX:
case TYPE_HEARING_AID:
case TYPE_BUILTIN_SPEAKER_SAFE:
case TYPE_BLE_HEADSET:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 536afd4f712e..4627be3f5810 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -896,6 +896,30 @@ public class AudioManager {
*/
public static final int USE_DEFAULT_STREAM_TYPE = Integer.MIN_VALUE;
+ /** @hide */
+ @IntDef(flag = false, prefix = "DEVICE_STATE", value = {
+ DEVICE_CONNECTION_STATE_DISCONNECTED,
+ DEVICE_CONNECTION_STATE_CONNECTED}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceConnectionState {}
+
+ /**
+ * @hide The device connection state for disconnected devices.
+ */
+ @SystemApi
+ @SuppressLint("UnflaggedApi") // b/373465238
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int DEVICE_CONNECTION_STATE_DISCONNECTED = 0;
+
+ /**
+ * @hide The device connection state for connected devices.
+ */
+ @SystemApi
+ @SuppressLint("UnflaggedApi") // b/373465238
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int DEVICE_CONNECTION_STATE_CONNECTED = 1;
+
private static IAudioService sService;
/**
@@ -6726,14 +6750,16 @@ public class AudioManager {
}
/**
+ * @hide
* Indicate wired accessory connection state change and attributes.
- * @param state new connection state: 1 connected, 0 disconnected
* @param attributes attributes of the connected device
- * {@hide}
+ * @param state new connection state
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @SuppressLint("UnflaggedApi") // b/373465238
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
- public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, int state) {
+ public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes,
+ @DeviceConnectionState int state) {
final IAudioService service = getService();
try {
service.setWiredDeviceConnectionState(attributes, state,
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 65fd51b9ece9..c1d73f9033cf 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -753,9 +753,11 @@ public class AidlConversion {
break;
case AudioSystem.DEVICE_IN_REMOTE_SUBMIX:
aidl.type = AudioDeviceType.IN_SUBMIX;
+ aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL;
break;
case AudioSystem.DEVICE_OUT_REMOTE_SUBMIX:
aidl.type = AudioDeviceType.OUT_SUBMIX;
+ aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL;
break;
case AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET:
aidl.type = AudioDeviceType.IN_DOCK;
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
index 121f5492a5ab..36960002e220 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
@@ -41,6 +41,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> {
"/system_ext/etc/NOTICE.xml.gz",
"/vendor_dlkm/etc/NOTICE.xml.gz",
"/odm_dlkm/etc/NOTICE.xml.gz",
+ "/system_dlkm/etc/NOTICE.xml.gz",
};
static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 95b765b2bf86..3d2c931d99e3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1118,6 +1118,13 @@ flag {
}
flag {
+ name: "communal_standalone_support"
+ namespace: "systemui"
+ description: "Support communal features without a dock"
+ bug: "352301247"
+}
+
+flag {
name: "dream_overlay_updated_font"
namespace: "systemui"
description: "Flag to enable updated font settings for dream overlay"
@@ -1560,6 +1567,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/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/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 834a7f5220ab..4fa1984661da 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -375,9 +375,13 @@ fun SceneScope.NotificationScrollingStack(
LaunchedEffect(shadeScrollState) { viewModel.setScrollState(shadeScrollState) }
// if contentHeight drops below minimum visible scrim height while scrim is
- // expanded, reset scrim offset.
- LaunchedEffect(stackHeight, scrimOffset) {
- snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
+ // expanded and IME is not showing, reset scrim offset.
+ LaunchedEffect(stackHeight, scrimOffset, imeTop) {
+ snapshotFlow {
+ stackHeight.intValue < minVisibleScrimHeight() &&
+ scrimOffset.value < 0f &&
+ imeTop.floatValue <= 0f
+ }
.collect { shouldCollapse -> if (shouldCollapse) scrimOffset.animateTo(0f, tween()) }
}
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/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 8e838f396b64..a89e6fb35fa8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,12 +19,14 @@ import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.plugins.clocks.AxisType
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
+import com.android.systemui.plugins.clocks.ClockReactiveAxis
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.shared.clocks.view.HorizontalAlignment
import com.android.systemui.shared.clocks.view.VerticalAlignment
@@ -85,8 +87,21 @@ class DefaultClockProvider(
// TODO(b/352049256): Update placeholder to actual resource
resources.getDrawable(R.drawable.clock_default_thumbnail, null),
isReactiveToTone = true,
- isReactiveToTouch = isClockReactiveVariantsEnabled,
- axes = listOf(), // TODO: Ater some picker definition
+ // TODO(b/364673969): Populate the rest of this
+ axes =
+ if (isClockReactiveVariantsEnabled)
+ listOf(
+ ClockReactiveAxis(
+ key = "wdth",
+ type = AxisType.Slider,
+ maxValue = 1000f,
+ minValue = 100f,
+ currentValue = 400f,
+ name = "Width",
+ description = "Glyph Width",
+ )
+ )
+ else listOf(),
)
}
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 3827dfa533cb..d5a7c89e6c71 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
@@ -64,7 +64,6 @@ import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -116,36 +115,18 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
kosmos.aodBurnInViewModel = aodBurnInViewModel
}
- val testScope = kosmos.testScope
- val configurationRepository
- get() = kosmos.fakeConfigurationRepository
-
- val keyguardRepository
- get() = kosmos.fakeKeyguardRepository
-
- val keyguardInteractor
- get() = kosmos.keyguardInteractor
-
- val keyguardRootViewModel
- get() = kosmos.keyguardRootViewModel
-
- val keyguardTransitionRepository
- get() = kosmos.fakeKeyguardTransitionRepository
-
- val shadeTestUtil
- get() = kosmos.shadeTestUtil
-
- val sharedNotificationContainerInteractor
- get() = kosmos.sharedNotificationContainerInteractor
-
- val largeScreenHeaderHelper
- get() = kosmos.mockLargeScreenHeaderHelper
-
- val communalSceneRepository
- get() = kosmos.communalSceneRepository
-
- val shadeRepository
- get() = kosmos.fakeShadeRepository
+ private val testScope = kosmos.testScope
+ private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val keyguardRootViewModel by lazy { kosmos.keyguardRootViewModel }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+ private val sharedNotificationContainerInteractor by lazy {
+ kosmos.sharedNotificationContainerInteractor
+ }
+ private val largeScreenHeaderHelper by lazy { kosmos.mockLargeScreenHeaderHelper }
+ private val communalSceneRepository by lazy { kosmos.communalSceneRepository }
lateinit var underTest: SharedNotificationContainerViewModel
@@ -637,6 +618,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)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index f975c4f13ff4..e264264d8c6c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -182,7 +182,7 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
interface ClockEvents {
@get:ProtectedReturn("return false;")
/** Set to enable or disable swipe interaction */
- var isReactiveTouchInteractionEnabled: Boolean
+ var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone)
@@ -322,9 +322,6 @@ constructor(
/** True if the clock will react to tone changes in the seed color */
val isReactiveToTone: Boolean = true,
- /** True if the clock is capable of changing style in reaction to touches */
- val isReactiveToTouch: Boolean = false,
-
/** Font axes that can be modified on this clock */
val axes: List<ClockReactiveAxis> = listOf(),
)
diff --git a/packages/SystemUI/res/drawable/volume_background_top.xml b/packages/SystemUI/res/drawable/volume_background_top.xml
index 3cd87fc32061..75849beeb016 100644
--- a/packages/SystemUI/res/drawable/volume_background_top.xml
+++ b/packages/SystemUI/res/drawable/volume_background_top.xml
@@ -1,27 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item>
<shape>
- <size android:width="@dimen/volume_dialog_panel_width" />
- <solid android:color="?androidprv:attr/colorSurface" />
- <corners android:topLeftRadius="@dimen/volume_dialog_panel_width_half"
- android:topRightRadius="@dimen/volume_dialog_panel_width_half"/>
+ <size android:width="@dimen/volume_dialog_width" />
+ <solid android:color="?androidprv:attr/materialColorSurface" />
+ <corners android:topLeftRadius="@dimen/volume_dialog_background_corner_radius"
+ android:topRightRadius="@dimen/volume_dialog_background_corner_radius"/>
</shape>
</item>
-</layer-list> \ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/volume_background_top_legacy.xml b/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
new file mode 100644
index 000000000000..3cd87fc32061
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item>
+ <shape>
+ <size android:width="@dimen/volume_dialog_panel_width" />
+ <solid android:color="?androidprv:attr/colorSurface" />
+ <corners android:topLeftRadius="@dimen/volume_dialog_panel_width_half"
+ android:topRightRadius="@dimen/volume_dialog_panel_width_half"/>
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_dialog_background.xml b/packages/SystemUI/res/drawable/volume_dialog_background.xml
index 98e75058fd3e..7d7498feeba6 100644
--- a/packages/SystemUI/res/drawable/volume_dialog_background.xml
+++ b/packages/SystemUI/res/drawable/volume_dialog_background.xml
@@ -18,5 +18,5 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/volume_dialog_background_corner_radius" />
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/materialColorSurface" />
</shape>
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
index 5e7cb12d1c5f..df8521a15f6d 100644
--- a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
@@ -15,10 +14,11 @@
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:paddingMode="stack" >
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:paddingMode="stack" >
<size
- android:height="@dimen/volume_ringer_drawer_item_size"
- android:width="@dimen/volume_ringer_drawer_item_size" />
- <solid android:color="?android:attr/colorAccent" />
- <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" />
+ android:height="@dimen/volume_ringer_item_size"
+ android:width="@dimen/volume_ringer_item_size" />
+ <solid android:color="?androidprv:attr/materialColorPrimary" />
+ <corners android:radius="360dp" />
</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml
new file mode 100644
index 000000000000..5e7cb12d1c5f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingMode="stack" >
+ <size
+ android:height="@dimen/volume_ringer_drawer_item_size"
+ android:width="@dimen/volume_ringer_drawer_item_size" />
+ <solid android:color="?android:attr/colorAccent" />
+ <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
new file mode 100644
index 000000000000..7ddd57b7c34e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <size android:width="@dimen/volume_ringer_item_size" android:height="@dimen/volume_ringer_item_size"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+ <corners android:radius="@dimen/volume_ringer_item_radius" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
index 08edf59000b8..de346db41252 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
@@ -44,7 +44,7 @@
android:clipChildren="false"
android:gravity="right">
- <include layout="@layout/volume_ringer_drawer" />
+ <include layout="@layout/volume_ringer_drawer_legacy" />
<FrameLayout
android:visibility="gone"
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 3013e905e950..8b39e5eb341f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -45,10 +45,7 @@
android:paddingVertical="@dimen/volume_dialog_vertical_padding"
android:showDividers="middle">
- <FrameLayout
- android:id="@+id/volume_dialog_ringer_button"
- android:layout_width="@dimen/volume_dialog_button_size"
- android:layout_height="@dimen/volume_dialog_button_size" />
+ <include layout="@layout/volume_ringer_drawer" />
<include layout="@layout/volume_dialog_slider" />
diff --git a/packages/SystemUI/res/layout/volume_dialog_legacy.xml b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
index 39a1f1f9b85d..9010ab764da0 100644
--- a/packages/SystemUI/res/layout/volume_dialog_legacy.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
@@ -45,7 +45,7 @@
android:orientation="vertical"
android:gravity="right">
- <include layout="@layout/volume_ringer_drawer" />
+ <include layout="@layout/volume_ringer_drawer_legacy" />
<FrameLayout
android:visibility="gone"
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 8f1e0610853f..94b55b18c64f 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -1,35 +1,32 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/volume_ringer_and_drawer_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
- android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingTop="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
- android:background="@drawable/volume_background_top"
+ android:paddingLeft="@dimen/volume_dialog_ringer_container_padding"
+ android:paddingTop="@dimen/volume_dialog_ringer_container_padding"
+ android:paddingRight="@dimen/volume_dialog_ringer_container_padding"
android:layoutDirection="ltr"
android:clipToPadding="false"
- android:clipChildren="false">
+ android:clipChildren="false"
+ android:background="@drawable/volume_background_top">
<!-- Drawer view, invisible by default. -->
<FrameLayout
@@ -37,15 +34,15 @@
android:alpha="0.0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/volume_drawer_bg"
android:orientation="vertical">
<!-- View that is animated to a tapped ringer selection, so it appears selected. -->
<FrameLayout
android:id="@+id/volume_drawer_selection_background"
android:alpha="0.0"
- android:layout_width="@dimen/volume_ringer_drawer_item_size"
- android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_width="@dimen/volume_ringer_item_size"
+ android:layout_marginBottom="8dp"
+ android:layout_height="@dimen/volume_ringer_item_size"
android:layout_gravity="bottom|right"
android:background="@drawable/volume_drawer_selection_bg" />
@@ -57,54 +54,60 @@
<FrameLayout
android:id="@+id/volume_drawer_vibrate"
- android:layout_width="@dimen/volume_ringer_drawer_item_size"
- android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_width="@dimen/volume_ringer_item_size"
+ android:layout_height="@dimen/volume_ringer_item_size"
+ android:layout_marginBottom="8dp"
android:contentDescription="@string/volume_ringer_hint_vibrate"
- android:gravity="center">
+ android:gravity="center"
+ android:background="@drawable/volume_ringer_item_bg">
<ImageView
android:id="@+id/volume_drawer_vibrate_icon"
- android:layout_width="@dimen/volume_ringer_drawer_icon_size"
- android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_width="@dimen/volume_ringer_icon_size"
+ android:layout_height="@dimen/volume_ringer_icon_size"
android:layout_gravity="center"
android:src="@drawable/ic_volume_ringer_vibrate"
- android:tint="?android:attr/textColorPrimary" />
+ android:tint="?androidprv:attr/materialColorOnSurface" />
</FrameLayout>
<FrameLayout
android:id="@+id/volume_drawer_mute"
- android:layout_width="@dimen/volume_ringer_drawer_item_size"
- android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_width="@dimen/volume_ringer_item_size"
+ android:layout_height="@dimen/volume_ringer_item_size"
+ android:layout_marginBottom="8dp"
android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
android:contentDescription="@string/volume_ringer_hint_mute"
- android:gravity="center">
+ android:gravity="center"
+ android:background="@drawable/volume_ringer_item_bg">
<ImageView
android:id="@+id/volume_drawer_mute_icon"
- android:layout_width="@dimen/volume_ringer_drawer_icon_size"
- android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_width="@dimen/volume_ringer_icon_size"
+ android:layout_height="@dimen/volume_ringer_icon_size"
android:layout_gravity="center"
android:src="@drawable/ic_speaker_mute"
- android:tint="?android:attr/textColorPrimary" />
+ android:tint="?androidprv:attr/materialColorOnSurface" />
</FrameLayout>
<FrameLayout
android:id="@+id/volume_drawer_normal"
- android:layout_width="@dimen/volume_ringer_drawer_item_size"
- android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_width="@dimen/volume_ringer_item_size"
+ android:layout_height="@dimen/volume_ringer_item_size"
+ android:layout_marginBottom="8dp"
android:accessibilityTraversalAfter="@id/volume_drawer_mute"
android:contentDescription="@string/volume_ringer_hint_unmute"
- android:gravity="center">
+ android:gravity="center"
+ android:background="@drawable/volume_ringer_item_bg">
<ImageView
android:id="@+id/volume_drawer_normal_icon"
- android:layout_width="@dimen/volume_ringer_drawer_icon_size"
- android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_width="@dimen/volume_ringer_icon_size"
+ android:layout_height="@dimen/volume_ringer_icon_size"
android:layout_gravity="center"
android:src="@drawable/ic_speaker_on"
- android:tint="?android:attr/textColorPrimary" />
+ android:tint="?androidprv:attr/materialColorOnSurface" />
</FrameLayout>
@@ -116,18 +119,19 @@
position in the drawer. When the drawer is closed, it animates back. -->
<FrameLayout
android:id="@+id/volume_new_ringer_active_icon_container"
- android:layout_width="@dimen/volume_ringer_drawer_item_size"
- android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_width="@dimen/volume_ringer_item_size"
+ android:layout_height="@dimen/volume_ringer_item_size"
+ android:layout_marginBottom="8dp"
android:layout_gravity="bottom|right"
android:contentDescription="@string/volume_ringer_change"
android:background="@drawable/volume_drawer_selection_bg">
<ImageView
android:id="@+id/volume_new_ringer_active_icon"
- android:layout_width="@dimen/volume_ringer_drawer_icon_size"
- android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_width="@dimen/volume_ringer_icon_size"
+ android:layout_height="@dimen/volume_ringer_icon_size"
android:layout_gravity="center"
- android:tint="?android:attr/textColorPrimaryInverse"
+ android:tint="?androidprv:attr/materialColorOnPrimary"
android:src="@drawable/ic_volume_media" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml b/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
new file mode 100644
index 000000000000..0efbc6d651dc
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/volume_ringer_and_drawer_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingTop="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
+ android:background="@drawable/volume_background_top_legacy"
+ android:layoutDirection="ltr"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <!-- Drawer view, invisible by default. -->
+ <FrameLayout
+ android:id="@+id/volume_drawer_container"
+ android:alpha="0.0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_drawer_bg"
+ android:orientation="vertical">
+
+ <!-- View that is animated to a tapped ringer selection, so it appears selected. -->
+ <FrameLayout
+ android:id="@+id/volume_drawer_selection_background"
+ android:alpha="0.0"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_gravity="bottom|right"
+ android:background="@drawable/volume_drawer_selection_bg_legacy" />
+
+ <LinearLayout
+ android:id="@+id/volume_drawer_options"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/volume_drawer_vibrate"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:contentDescription="@string/volume_ringer_hint_vibrate"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/volume_drawer_vibrate_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_volume_ringer_vibrate"
+ android:tint="?android:attr/textColorPrimary" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/volume_drawer_mute"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
+ android:contentDescription="@string/volume_ringer_hint_mute"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/volume_drawer_mute_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_speaker_mute"
+ android:tint="?android:attr/textColorPrimary" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/volume_drawer_normal"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:accessibilityTraversalAfter="@id/volume_drawer_mute"
+ android:contentDescription="@string/volume_ringer_hint_unmute"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/volume_drawer_normal_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_speaker_on"
+ android:tint="?android:attr/textColorPrimary" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ </FrameLayout>
+
+ <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding
+ position in the drawer. When the drawer is closed, it animates back. -->
+ <FrameLayout
+ android:id="@+id/volume_new_ringer_active_icon_container"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_gravity="bottom|right"
+ android:contentDescription="@string/volume_ringer_change"
+ android:background="@drawable/volume_drawer_selection_bg_legacy">
+
+ <ImageView
+ android:id="@+id/volume_new_ringer_active_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:tint="?android:attr/textColorPrimaryInverse"
+ android:src="@drawable/ic_volume_media" />
+
+ </FrameLayout>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 87e6aa061a50..c2d942ff5851 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2064,5 +2064,11 @@
<dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
<dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+
+ <dimen name="volume_dialog_ringer_container_padding">10dp</dimen>
+ <dimen name="volume_ringer_item_size">40dp</dimen>
+ <dimen name="volume_ringer_icon_size">20dp</dimen>
+ <dimen name="volume_ringer_item_radius">12dp</dimen>
+ <dimen name="volume_dialog_ringer_item_margin_bottom">8dp</dimen>
<!-- Volume end -->
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b3ea75d00917..8b2cf7a735de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -80,6 +80,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.usb.UsbManager;
import android.nfc.NfcAdapter;
+import android.os.BatteryManager;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
@@ -345,9 +346,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Device provisioning state
private boolean mDeviceProvisioned;
- // Battery status
+ // Battery status (null until first update is received)
@VisibleForTesting
- BatteryStatus mBatteryStatus;
+ BatteryStatus mBatteryStatus = null;
@VisibleForTesting
boolean mIncompatibleCharger;
@@ -2367,9 +2368,27 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
watchForDeviceProvisioning();
}
- // Take a guess at initial SIM state, battery status and PLMN until we get an update
- mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ -1, /* plugged= */
- 0, CHARGING_POLICY_DEFAULT, /* maxChargingWattage= */0, /* present= */true);
+ // Request the initial battery level
+ mBackgroundExecutor.execute(() -> {
+ BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class);
+ int level = -1;
+ if (batteryManager != null) {
+ int capacity = batteryManager.getIntProperty(
+ BatteryManager.BATTERY_PROPERTY_CAPACITY);
+ if (capacity >= 0 && capacity <= 100) {
+ level = capacity;
+ }
+ }
+ // Don't override if a valid battery status update has come in
+ final BatteryStatus status = new BatteryStatus(BATTERY_STATUS_UNKNOWN,
+ /* level= */ level, /* plugged= */ 0, CHARGING_POLICY_DEFAULT,
+ /* maxChargingWattage= */0, /* present= */true);
+ mMainExecutor.execute(() -> {
+ if (mBatteryStatus == null) {
+ handleBatteryUpdate(status);
+ }
+ });
+ });
// Watch for interesting updates
final IntentFilter filter = new IntentFilter();
@@ -3591,6 +3610,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+ if (old == null) {
+ return true;
+ }
final boolean nowPluggedIn = current.isPluggedIn();
final boolean wasPluggedIn = old.isPluggedIn();
final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
@@ -3687,7 +3709,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
// Notify listener of the current state
- callback.onRefreshBatteryInfo(mBatteryStatus);
+ if (mBatteryStatus != null) {
+ callback.onRefreshBatteryInfo(mBatteryStatus);
+ }
callback.onTimeChanged();
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
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/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/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/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/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/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/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 129d4cee9cdb..777142194221 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -596,10 +596,12 @@ public class AmbientState implements Dumpable {
}
public void setContentHeight(int contentHeight) {
+ SceneContainerFlag.assertInLegacyMode();
mContentHeight = contentHeight;
}
public float getContentHeight() {
+ SceneContainerFlag.assertInLegacyMode();
return mContentHeight;
}
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 1d4916c6cd82..dde83b9bd7a7 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
@@ -615,13 +615,13 @@ public class NotificationStackScrollLayout
if (SceneContainerFlag.isEnabled()) {
return mScrollViewFields.getScrollState().isScrolledToTop();
} else {
- return mOwnScrollY == 0;
+ return getOwnScrollY() == 0;
}
}
@Override
public boolean isScrolledToBottom() {
- return mOwnScrollY >= getScrollRange();
+ return getOwnScrollY() >= getScrollRange();
}
@Override
@@ -900,11 +900,11 @@ public class NotificationStackScrollLayout
drawDebugInfo(canvas, y, Color.LTGRAY,
/* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = " + y);
- y = (int) (mAmbientState.getStackY() + mIntrinsicContentHeight);
+ y = (int) (mAmbientState.getStackY() + getIntrinsicContentHeight());
drawDebugInfo(canvas, y, Color.YELLOW,
/* label= */ "mAmbientState.getStackY() + mIntrinsicContentHeight = " + y);
- y = mContentHeight;
+ y = getContentHeight();
drawDebugInfo(canvas, y, Color.MAGENTA,
/* label= */ "mContentHeight = " + y);
@@ -1374,7 +1374,7 @@ public class NotificationStackScrollLayout
/**
* Updates the children views according to the stack scroll algorithm. Call this whenever
- * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
+ * modifications to {@link #getOwnScrollY()} are performed to reflect it in the view layout.
*/
private void updateChildren() {
Trace.beginSection("NSSL#updateChildren");
@@ -1404,11 +1404,11 @@ public class NotificationStackScrollLayout
if (mChildrenToAddAnimated.contains(child)) {
final int startingPosition = getPositionInLinearLayout(child);
final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements;
- if (startingPosition < mOwnScrollY) {
+ if (startingPosition < getOwnScrollY()) {
// This child starts off screen, so let's keep it offscreen to keep the
// others visible
- setOwnScrollY(mOwnScrollY + childHeight);
+ setOwnScrollY(getOwnScrollY() + childHeight);
}
}
}
@@ -1428,7 +1428,7 @@ public class NotificationStackScrollLayout
targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
// Only apply the scroll if we're scrolling the view upwards, or the view is so
// far up that it is not visible anymore.
- if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+ if (getOwnScrollY() < targetScroll || outOfViewScroll < getOwnScrollY()) {
setOwnScrollY(targetScroll);
}
}
@@ -1452,7 +1452,7 @@ public class NotificationStackScrollLayout
return;
}
int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) {
+ if (scrollRange < getOwnScrollY() && !mAmbientState.isClearAllInProgress()) {
// if the scroll boundary updates the position of the stack,
boolean animateStackY = scrollRange < getScrollAmountToScrollBoundary()
&& mAnimateStackYForContentHeightChange;
@@ -1586,7 +1586,7 @@ public class NotificationStackScrollLayout
if (mMaxDisplayedNotifications != -1) {
// The stack intrinsic height already contains the correct value when there is a limit
// in the max number of notifications (e.g. as in keyguard).
- stackEndHeight = mIntrinsicContentHeight;
+ stackEndHeight = getIntrinsicContentHeight();
} else {
stackEndHeight = Math.max(0f, height - bottomMargin - topPadding);
}
@@ -1692,7 +1692,8 @@ public class NotificationStackScrollLayout
if (mShouldShowShelfOnly) {
stackHeight = getTopPadding() + mShelf.getIntrinsicHeight();
} else if (mQsFullScreen) {
- int stackStartPosition = mContentHeight - getTopPadding() + getIntrinsicPadding();
+ int stackStartPosition =
+ getContentHeight() - getTopPadding() + getIntrinsicPadding();
int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
if (stackStartPosition <= stackEndPosition) {
stackHeight = stackEndPosition;
@@ -1761,14 +1762,6 @@ public class NotificationStackScrollLayout
updateClipping();
}
- /**
- * Return the height of the content ignoring the footer.
- */
- public int getIntrinsicContentHeight() {
- SceneContainerFlag.assertInLegacyMode();
- return (int) mIntrinsicContentHeight;
- }
-
public void updateClipping() {
boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
&& !mHeadsUpAnimatingAway;
@@ -2072,8 +2065,8 @@ public class NotificationStackScrollLayout
// Only apply the scroll if we're scrolling the view upwards, or the view is so far up
// that it is not visible anymore.
- if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+ if (getOwnScrollY() < targetScroll || outOfViewScroll < getOwnScrollY()) {
+ mScroller.startScroll(mScrollX, getOwnScrollY(), 0, targetScroll - getOwnScrollY());
mDontReportNextOverScroll = true;
animateScroll();
return true;
@@ -2107,7 +2100,7 @@ public class NotificationStackScrollLayout
}
int range = getScrollRange();
- if (mOwnScrollY > range) {
+ if (getOwnScrollY() > range) {
setOwnScrollY(range);
}
}
@@ -2200,7 +2193,7 @@ public class NotificationStackScrollLayout
// Top overScroll might not grab all scrolling motion,
// we have to scroll as well.
float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
- float newScrollY = mOwnScrollY + scrollAmount;
+ float newScrollY = getOwnScrollY() + scrollAmount;
if (newScrollY > range) {
if (!mExpandedInThisMotion) {
float currentBottomPixels = getCurrentOverScrolledPixels(false);
@@ -2233,7 +2226,7 @@ public class NotificationStackScrollLayout
// Bottom overScroll might not grab all scrolling motion,
// we have to scroll as well.
float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
- float newScrollY = mOwnScrollY + scrollAmount;
+ float newScrollY = getOwnScrollY() + scrollAmount;
if (newScrollY < 0) {
float currentTopPixels = getCurrentOverScrolledPixels(true);
// We overScroll on the top
@@ -2273,7 +2266,7 @@ public class NotificationStackScrollLayout
private void animateScroll() {
if (mScroller.computeScrollOffset()) {
- int oldY = mOwnScrollY;
+ int oldY = getOwnScrollY();
int y = mScroller.getCurrY();
if (oldY != y) {
@@ -2460,8 +2453,8 @@ public class NotificationStackScrollLayout
springBack();
} else {
float overScrollTop = getCurrentOverScrollAmount(true);
- if (mOwnScrollY < 0) {
- notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
+ if (getOwnScrollY() < 0) {
+ notifyOverscrollTopListener(-getOwnScrollY(), isRubberbanded(true));
} else {
notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
}
@@ -2477,19 +2470,19 @@ public class NotificationStackScrollLayout
*/
private void springBack() {
int scrollRange = getScrollRange();
- boolean overScrolledTop = mOwnScrollY <= 0;
- boolean overScrolledBottom = mOwnScrollY >= scrollRange;
+ boolean overScrolledTop = getOwnScrollY() <= 0;
+ boolean overScrolledBottom = getOwnScrollY() >= scrollRange;
if (overScrolledTop || overScrolledBottom) {
boolean onTop;
float newAmount;
if (overScrolledTop) {
onTop = true;
- newAmount = -mOwnScrollY;
+ newAmount = -getOwnScrollY();
setOwnScrollY(0);
mDontReportNextOverScroll = true;
} else {
onTop = false;
- newAmount = mOwnScrollY - scrollRange;
+ newAmount = getOwnScrollY() - scrollRange;
setOwnScrollY(scrollRange);
}
setOverScrollAmount(newAmount, onTop, false);
@@ -2506,7 +2499,7 @@ public class NotificationStackScrollLayout
}
// In current design, it only use the top HUN to treat all of HUNs
// although there are more than one HUNs
- int contentHeight = mContentHeight;
+ int contentHeight = getContentHeight();
if (!isExpanded() && mInHeadsUpPinnedMode) {
contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
}
@@ -2642,17 +2635,16 @@ public class NotificationStackScrollLayout
(int) scrimTopPadding + (int) mNotificationStackSizeCalculator.computeHeight(
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
- mIntrinsicContentHeight = height;
+ setIntrinsicContentHeight(height);
// 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);
+ setContentHeight(
+ (int) (height + Math.max(getIntrinsicPadding(), getTopPadding()) + mBottomPadding));
updateScrollability();
clampScrollPosition();
updateStackPosition();
- mAmbientState.setContentHeight(mContentHeight);
+ mAmbientState.setContentHeight(getContentHeight());
}
@Override
@@ -2794,7 +2786,7 @@ public class NotificationStackScrollLayout
float topAmount = getCurrentOverScrollAmount(true);
float bottomAmount = getCurrentOverScrollAmount(false);
if (velocityY < 0 && topAmount > 0) {
- setOwnScrollY(mOwnScrollY - (int) topAmount);
+ setOwnScrollY(getOwnScrollY() - (int) topAmount);
if (!mShouldUseSplitNotificationShade) {
mDontReportNextOverScroll = true;
setOverScrollAmount(0, true, false);
@@ -2802,7 +2794,7 @@ public class NotificationStackScrollLayout
mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
* mOverflingDistance + topAmount;
} else if (velocityY > 0 && bottomAmount > 0) {
- setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+ setOwnScrollY((int) (getOwnScrollY() + bottomAmount));
setOverScrollAmount(0, false, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f
* getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2816,8 +2808,8 @@ public class NotificationStackScrollLayout
if (mExpandedInThisMotion) {
minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
}
- mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
- mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
+ mScroller.fling(mScrollX, getOwnScrollY(), 1, velocityY, 0, 0, 0, minScrollY, 0,
+ mExpandedInThisMotion && getOwnScrollY() >= 0 ? 0 : Integer.MAX_VALUE / 2);
animateScroll();
}
@@ -3138,11 +3130,11 @@ public class NotificationStackScrollLayout
final int scrollBoundaryStart = getScrollAmountToScrollBoundary();
mAnimateStackYForContentHeightChange = true;
// This is reset onLayout
- if (endPosition <= mOwnScrollY - scrollBoundaryStart) {
+ if (endPosition <= getOwnScrollY() - scrollBoundaryStart) {
// This child is fully scrolled of the top, so we have to deduct its height from the
// scrollPosition
- setOwnScrollY(mOwnScrollY - childHeight);
- } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) {
+ setOwnScrollY(getOwnScrollY() - childHeight);
+ } else if (startingPosition < getOwnScrollY() - scrollBoundaryStart) {
// This child is currently being scrolled into, set the scroll position to the
// start of this child
setOwnScrollY(startingPosition + scrollBoundaryStart);
@@ -3764,7 +3756,7 @@ public class NotificationStackScrollLayout
if (vscroll != 0) {
final int delta = (int) (vscroll * getVerticalScrollFactor());
final int range = getScrollRange();
- int oldScrollY = mOwnScrollY;
+ int oldScrollY = getOwnScrollY();
int newScrollY = oldScrollY - delta;
if (newScrollY < 0) {
newScrollY = 0;
@@ -3881,7 +3873,7 @@ public class NotificationStackScrollLayout
if (scrollAmount != 0.0f) {
// The scrolling motion could not be compensated with the
// existing overScroll, we have to scroll the view
- customOverScrollBy((int) scrollAmount, mOwnScrollY,
+ customOverScrollBy((int) scrollAmount, getOwnScrollY(),
range, getHeight() / 2);
// If we're scrolling, leavebehinds should be dismissed
mController.checkSnoozeLeavebehind();
@@ -3913,7 +3905,7 @@ public class NotificationStackScrollLayout
onOverScrollFling(false, initialVelocity);
}
} else {
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+ if (mScroller.springBack(mScrollX, getOwnScrollY(), 0, 0, 0,
getScrollRange())) {
animateScroll();
}
@@ -3927,7 +3919,7 @@ public class NotificationStackScrollLayout
break;
case ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+ if (mScroller.springBack(mScrollX, getOwnScrollY(), 0, 0, 0,
getScrollRange())) {
animateScroll();
}
@@ -4213,7 +4205,7 @@ public class NotificationStackScrollLayout
setIsBeingDragged(false);
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
+ if (mScroller.springBack(mScrollX, getOwnScrollY(), 0, 0, 0, getScrollRange())) {
animateScroll();
}
break;
@@ -4311,10 +4303,10 @@ public class NotificationStackScrollLayout
getHeight() - mPaddingBottom - getTopPadding() - mPaddingTop
- mShelf.getIntrinsicHeight();
final int targetScrollY = Math.max(0,
- Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
- if (targetScrollY != mOwnScrollY) {
- mScroller.startScroll(mScrollX, mOwnScrollY, 0,
- targetScrollY - mOwnScrollY);
+ Math.min(getOwnScrollY() + direction * viewportHeight, getScrollRange()));
+ if (targetScrollY != getOwnScrollY()) {
+ mScroller.startScroll(mScrollX, getOwnScrollY(), 0,
+ targetScrollY - getOwnScrollY());
animateScroll();
return true;
}
@@ -4356,9 +4348,9 @@ public class NotificationStackScrollLayout
// it is based on notifications bottom, which is lower on split shade.
// Here we prefer to use at least a minimum height defined for split shade.
// Otherwise the expansion motion is too fast.
- contentHeight = Math.max(mSplitShadeMinContentHeight, mContentHeight);
+ contentHeight = Math.max(mSplitShadeMinContentHeight, getContentHeight());
} else {
- contentHeight = mContentHeight;
+ contentHeight = getContentHeight();
}
return Math.max(mMaxLayoutHeight - contentHeight, 0);
}
@@ -4568,7 +4560,7 @@ public class NotificationStackScrollLayout
float diff = endPosition - layoutEnd;
mScrollViewFields.sendSyntheticScroll(diff);
}
- setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+ setOwnScrollY((int) (getOwnScrollY() + endPosition - layoutEnd));
mDisallowScrollingInThisMotion = true;
}
}
@@ -5079,7 +5071,7 @@ public class NotificationStackScrollLayout
event.setScrollY(mScrollViewFields.getScrollState().getScrollPosition());
event.setMaxScrollY(mScrollViewFields.getScrollState().getMaxScrollPosition());
} else {
- event.setScrollY(mOwnScrollY);
+ event.setScrollY(getOwnScrollY());
event.setMaxScrollY(getScrollRange());
}
}
@@ -5285,8 +5277,8 @@ public class NotificationStackScrollLayout
// If notifications are scrolled,
// clear out scrollY by the time we push notifications offscreen
- if (mOwnScrollY > 0) {
- setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
+ if (getOwnScrollY() > 0) {
+ setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, mQsExpansionFraction));
}
if (!FooterViewRefactor.isEnabled() && footerAffected) {
updateFooter();
@@ -5294,6 +5286,15 @@ public class NotificationStackScrollLayout
}
@VisibleForTesting
+ int getOwnScrollY() {
+ if (SceneContainerFlag.isEnabled()) {
+ return 0;
+ } else {
+ return mOwnScrollY;
+ }
+ }
+
+ @VisibleForTesting
void setOwnScrollY(int ownScrollY) {
setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
}
@@ -5310,11 +5311,11 @@ public class NotificationStackScrollLayout
return;
}
- if (ownScrollY != mOwnScrollY) {
+ if (ownScrollY != getOwnScrollY()) {
// We still want to call the normal scrolled changed for accessibility reasons
- onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
+ onScrollChanged(mScrollX, ownScrollY, mScrollX, getOwnScrollY());
mOwnScrollY = ownScrollY;
- mAmbientState.setScrollY(mOwnScrollY);
+ mAmbientState.setScrollY(getOwnScrollY());
updateOnScrollChange();
updateStackPosition(animateStackYChangeListener);
}
@@ -5323,7 +5324,7 @@ public class NotificationStackScrollLayout
private void updateOnScrollChange() {
SceneContainerFlag.assertInLegacyMode();
if (mScrollListener != null) {
- mScrollListener.accept(mOwnScrollY);
+ mScrollListener.accept(getOwnScrollY());
}
updateForwardAndBackwardScrollability();
requestChildrenUpdate();
@@ -5516,12 +5517,7 @@ public class NotificationStackScrollLayout
println(pw, "hideAmount", mAmbientState.getHideAmount());
println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications);
- println(pw, "intrinsicContentHeight", mIntrinsicContentHeight);
- println(pw, "contentHeight", mContentHeight);
println(pw, "intrinsicPadding", mIntrinsicPadding);
- if (!SceneContainerFlag.isEnabled()) {
- println(pw, "topPadding", getTopPadding());
- }
println(pw, "bottomPadding", mBottomPadding);
dumpRoundedRectClipping(pw);
println(pw, "requestedClipBounds", mRequestedClipBounds);
@@ -5546,6 +5542,12 @@ public class NotificationStackScrollLayout
println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
mNotificationStackSizeCalculator.dump(pw, args);
mScrollViewFields.dump(pw);
+ if (!SceneContainerFlag.isEnabled()) {
+ // fields which will be removed with SceneContainer
+ println(pw, "intrinsicContentHeight", getIntrinsicContentHeight());
+ println(pw, "contentHeight", getContentHeight());
+ println(pw, "topPadding", getTopPadding());
+ }
});
pw.println();
pw.println("Contents:");
@@ -6890,7 +6892,7 @@ public class NotificationStackScrollLayout
public void expansionStateChanged(boolean isExpanding) {
mExpandingNotification = isExpanding;
if (!mExpandedInThisMotion) {
- mMaxScrollAfterExpand = mOwnScrollY;
+ mMaxScrollAfterExpand = getOwnScrollY();
mExpandedInThisMotion = true;
}
}
@@ -6946,4 +6948,31 @@ public class NotificationStackScrollLayout
void onAnimationEnd(
List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
}
+
+ // -------------------- Getters / Setters for the SceneContainer refactor ----------------------
+
+ /** Use {@link ScrollViewFields#intrinsicStackHeight}, when SceneContainerFlag is enabled. */
+ private int getContentHeight() {
+ SceneContainerFlag.assertInLegacyMode();
+ return mContentHeight;
+ }
+
+ private void setContentHeight(int contentHeight) {
+ SceneContainerFlag.assertInLegacyMode();
+ mContentHeight = contentHeight;
+ }
+
+ /**
+ * Use {@link ScrollViewFields#intrinsicStackHeight}, when SceneContainerFlag is enabled.
+ * @return the height of the content ignoring the footer.
+ */
+ public float getIntrinsicContentHeight() {
+ SceneContainerFlag.assertInLegacyMode();
+ return mIntrinsicContentHeight;
+ }
+
+ private void setIntrinsicContentHeight(float intrinsicContentHeight) {
+ SceneContainerFlag.assertInLegacyMode();
+ mIntrinsicContentHeight = intrinsicContentHeight;
+ }
}
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 400654698d5d..dc1a191ce233 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
@@ -1188,7 +1188,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
public int getIntrinsicContentHeight() {
SceneContainerFlag.assertInLegacyMode();
- return mView.getIntrinsicContentHeight();
+ return (int) mView.getIntrinsicContentHeight();
}
/**
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..e644815960aa 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,7 +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.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.SplitShadeStateController
import dagger.Lazy
import javax.inject.Inject
@@ -45,9 +45,8 @@ 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,
keyguardInteractor: KeyguardInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
@@ -66,23 +65,14 @@ constructor(
* distinctUntilChanged() to this would cause configurationBasedDimensions to miss configuration
* updates that affect other resources, like margins or the large screen header flag.
*/
- private val dimensionsUpdateEventsWithShouldUseSplitShade: Flow<Boolean> =
- if (SceneContainerFlag.isEnabled) {
- combine(
- configurationInteractor.onAnyConfigurationChange,
- shadeInteractor.get().isShadeLayoutWide,
- ) { _, isShadeLayoutWide ->
- isShadeLayoutWide
- }
- } else {
- configurationInteractor.onAnyConfigurationChange.map {
- splitShadeStateController.get().shouldUseSplitNotificationShade(context.resources)
- }
- }
-
+ @Deprecated("Use SharedNotificationContainerViewModel.ConfigurationBasedDimensions instead")
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
- dimensionsUpdateEventsWithShouldUseSplitShade
- .map { shouldUseSplitShade ->
+ configurationInteractor.onAnyConfigurationChange
+ .map {
+ val shouldUseSplitShade =
+ splitShadeStateController
+ .get()
+ .shouldUseSplitNotificationShade(context.resources)
with(context.resources) {
ConfigurationBasedDimensions(
useSplitShade = shouldUseSplitShade,
@@ -101,6 +91,10 @@ constructor(
}
}
.distinctUntilChanged()
+ get() {
+ SceneContainerFlag.assertInLegacyMode()
+ return field
+ }
/**
* The notification shelf can extend over the lock icon area if:
@@ -125,6 +119,7 @@ constructor(
_notificationStackChanged.value = _notificationStackChanged.value + 1
}
+ @Deprecated("Use SharedNotificationContainerViewModel.ConfigurationBasedDimensions instead")
data class ConfigurationBasedDimensions(
val useSplitShade: Boolean,
val useLargeScreenHeader: Boolean,
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 99e5fdad85bd..e6663d51aed5 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
@@ -19,9 +19,11 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.content.Context
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.flow.flowName
import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -66,10 +68,14 @@ import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewM
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode.Dual
+import com.android.systemui.shade.shared.model.ShadeMode.Single
+import com.android.systemui.shade.shared.model.ShadeMode.Split
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
@@ -90,6 +96,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
@@ -108,6 +115,8 @@ constructor(
private val interactor: SharedNotificationContainerInteractor,
dumpManager: DumpManager,
@Application applicationScope: CoroutineScope,
+ private val context: Context,
+ configurationInteractor: ConfigurationInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -145,6 +154,7 @@ constructor(
private val communalSceneInteractor: CommunalSceneInteractor,
// Lazy because it's only used in the SceneContainer + Dual Shade configuration.
headsUpNotificationInteractor: Lazy<HeadsUpNotificationInteractor>,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
) : FlowDumperImpl(dumpManager) {
@@ -187,33 +197,62 @@ constructor(
@VisibleForTesting
val paddingTopDimen: Flow<Int> =
- interactor.configurationBasedDimensions
- .map {
- when {
- it.useLargeScreenHeader -> it.marginTopLargeScreen
- else -> it.marginTop
+ if (SceneContainerFlag.isEnabled) {
+ configurationInteractor.onAnyConfigurationChange.map {
+ with(context.resources) {
+ val useLargeScreenHeader =
+ getBoolean(R.bool.config_use_large_screen_shade_header)
+ if (useLargeScreenHeader) {
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+ } else {
+ getDimensionPixelSize(R.dimen.notification_panel_margin_top)
+ }
+ }
+ }
+ } else {
+ interactor.configurationBasedDimensions.map {
+ when {
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
}
}
.distinctUntilChanged()
.dumpWhileCollecting("paddingTopDimen")
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
- interactor.configurationBasedDimensions
- .map {
- val marginTop =
- when {
- // y position of the NSSL in the window needs to be 0 under scene container
- SceneContainerFlag.isEnabled -> 0
- it.useLargeScreenHeader -> it.marginTopLargeScreen
- else -> it.marginTop
+ if (SceneContainerFlag.isEnabled) {
+ combine(
+ shadeInteractor.isShadeLayoutWide,
+ configurationInteractor.onAnyConfigurationChange,
+ ) { isShadeLayoutWide, _ ->
+ with(context.resources) {
+ // TODO(b/338033836): Define separate horizontal margins for dual shade.
+ val marginHorizontal =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ ConfigurationBasedDimensions(
+ marginStart = if (isShadeLayoutWide) 0 else marginHorizontal,
+ marginEnd = marginHorizontal,
+ marginBottom =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+ // y position of the NSSL in the window needs to be 0 under scene
+ // container
+ marginTop = 0,
+ useSplitShade = isShadeLayoutWide,
+ )
}
- ConfigurationBasedDimensions(
- marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
- marginEnd = it.marginHorizontal,
- marginBottom = it.marginBottom,
- marginTop = marginTop,
- useSplitShade = it.useSplitShade,
- )
+ }
+ } else {
+ interactor.configurationBasedDimensions.map {
+ ConfigurationBasedDimensions(
+ marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+ marginEnd = it.marginHorizontal,
+ marginBottom = it.marginBottom,
+ marginTop =
+ if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+ useSplitShade = it.useSplitShade,
+ )
+ }
}
.distinctUntilChanged()
.dumpWhileCollecting("configurationBasedDimensions")
@@ -221,13 +260,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")
@@ -404,42 +445,60 @@ constructor(
* notifications unless in splitshade.
*/
private val alphaForShadeAndQsExpansion: Flow<Float> =
- if (DualShade.isEnabled) {
- combineTransform(
- headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
- shadeInteractor.shadeExpansion,
- shadeInteractor.qsExpansion,
- ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
- if (isHeadsUpOrAnimatingAway) {
- // Ensure HUNs will be visible in QS shade (at least while unlocked)
- emit(1f)
- } else if (shadeExpansion > 0f || qsExpansion > 0f) {
- // Fade out as QS shade expands
- emit(1f - qsExpansion)
- }
- }
- } else {
- interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions
- ->
- combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
- shadeExpansion,
- qsExpansion ->
- if (shadeExpansion > 0f || qsExpansion > 0f) {
- if (configurationBasedDimensions.useSplitShade) {
- emit(1f)
- } else if (qsExpansion == 1f) {
+ if (SceneContainerFlag.isEnabled) {
+ shadeInteractor.shadeMode.flatMapLatest { shadeMode ->
+ when (shadeMode) {
+ Single ->
+ combineTransform(
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
+ ) { shadeExpansion, qsExpansion ->
+ if (qsExpansion == 1f) {
// Ensure HUNs will be visible in QS shade (at least while unlocked)
emit(1f)
- } else {
+ } else if (shadeExpansion > 0f || qsExpansion > 0f) {
// Fade as QS shade expands
emit(1f - qsExpansion)
}
}
+ Split -> isAnyExpanded.filter { it }.map { 1f }
+ Dual ->
+ combineTransform(
+ headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
+ ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
+ if (isHeadsUpOrAnimatingAway) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
+ } else if (shadeExpansion > 0f || qsExpansion > 0f) {
+ // Fade out as QS shade expands
+ emit(1f - qsExpansion)
+ }
+ }
+ }
+ }
+ } else {
+ interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions ->
+ combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
+ shadeExpansion,
+ qsExpansion ->
+ if (shadeExpansion > 0f || qsExpansion > 0f) {
+ if (configurationBasedDimensions.useSplitShade) {
+ emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
+ } else {
+ // Fade as QS shade expands
+ emit(1f - qsExpansion)
+ }
}
}
}
- .onStart { emit(1f) }
- .dumpWhileCollecting("alphaForShadeAndQsExpansion")
+ }
+ .onStart { emit(1f) }
+ .dumpWhileCollecting("alphaForShadeAndQsExpansion")
val panelAlpha = keyguardInteractor.panelAlpha
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/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
new file mode 100644
index 000000000000..c05a0b265ee9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume.dialog.ringer.ui.binder
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+@VolumeDialogScope
+class VolumeDialogRingerViewBinder
+@Inject
+constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Factory) {
+
+ fun bind(view: View) {
+ with(view) {
+ repeatWhenAttached {
+ viewModel(
+ traceName = "VolumeDialogRingerViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelFactory.create() },
+ ) { viewModel ->
+ setSnapshotBinding {}
+ awaitCancellation()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
index eb9483f8ea68..cd535e420e18 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -27,6 +27,7 @@ import android.view.WindowManager
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
@@ -43,6 +44,7 @@ constructor(
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogViewBinder: VolumeDialogViewBinder,
private val slidersViewBinder: VolumeDialogSlidersViewBinder,
+ private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
private val gravityViewModel: VolumeDialogGravityViewModel,
) {
@@ -54,6 +56,7 @@ constructor(
dialog.setCanceledOnTouchOutside(true)
with(dialog.requireViewById<View>(R.id.volume_dialog_container)) {
+ volumeDialogRingerViewBinder.bind(this)
slidersViewBinder.bind(this)
settingsButtonViewBinder.bind(this)
volumeDialogViewBinder.bind(dialog, this)
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/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 83fc3e9f0c58..b1e9d89dfd42 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -23,7 +23,6 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.policy.splitShadeStateController
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +32,6 @@ val Kosmos.sharedNotificationContainerInteractor by
SharedNotificationContainerInteractor(
context = applicationContext,
splitShadeStateController = { splitShadeStateController },
- shadeInteractor = { shadeInteractor },
configurationInteractor = configurationInteractor,
keyguardInteractor = keyguardInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index a25a3c0c2044..7fbf4e495feb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.content.applicationContext
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -49,6 +51,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -61,6 +64,8 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
interactor = sharedNotificationContainerInteractor,
dumpManager = dumpManager,
applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ configurationInteractor = configurationInteractor,
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
@@ -94,6 +99,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
aodBurnInViewModel = aodBurnInViewModel,
communalSceneInteractor = communalSceneInteractor,
headsUpNotificationInteractor = { headsUpNotificationInteractor },
+ largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 6a1e319b4039..1082e6214935 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -670,6 +670,17 @@ public class SystemConfig {
}
private void readAllPermissions() {
+ readAllPermissionsFromXml();
+ readAllPermissionsFromEnvironment();
+
+ // Apply global feature removal last, after all features have been read.
+ // This only needs to happen once.
+ for (String featureName : mUnavailableFeatures) {
+ removeFeature(featureName);
+ }
+ }
+
+ private void readAllPermissionsFromXml() {
final XmlPullParser parser = Xml.newPullParser();
// Read configuration from system
@@ -1730,7 +1741,13 @@ public class SystemConfig {
} finally {
IoUtils.closeQuietly(permReader);
}
+ }
+ // Add features or permission dependent on global system properties (as
+ // opposed to XML permission files).
+ // This only needs to be called once after all features have been parsed
+ // from various partition/apex sources.
+ private void readAllPermissionsFromEnvironment() {
// Some devices can be field-converted to FBE, so offer to splice in
// those features if not already defined by the static config
if (StorageManager.isFileEncrypted()) {
@@ -1771,10 +1788,6 @@ public class SystemConfig {
addFeature(PackageManager.FEATURE_EROFS_LEGACY, 0);
}
}
-
- for (String featureName : mUnavailableFeatures) {
- removeFeature(featureName);
- }
}
private @Nullable SignedPackage parseEnhancedConfirmationTrustedPackage(XmlPullParser parser,
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/display/DisplayTopology.java b/services/core/java/com/android/server/display/DisplayTopology.java
index b01d61721c9c..fdadafeb98c9 100644
--- a/services/core/java/com/android/server/display/DisplayTopology.java
+++ b/services/core/java/com/android/server/display/DisplayTopology.java
@@ -16,7 +16,13 @@
package com.android.server.display;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT;
+
import android.annotation.Nullable;
+import android.graphics.RectF;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
@@ -25,16 +31,21 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.LinkedList;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Queue;
/**
* Represents the relative placement of extended displays.
+ * Does not support concurrent calls, so a lock should be held when calling into this class.
*/
class DisplayTopology {
private static final String TAG = "DisplayTopology";
+ private static final float EPSILON = 0.0001f;
/**
* The topology tree
@@ -58,7 +69,7 @@ class DisplayTopology {
* @param width The width of the display
* @param height The height of the display
*/
- void addDisplay(int displayId, double width, double height) {
+ void addDisplay(int displayId, float width, float height) {
addDisplay(displayId, width, height, /* shouldLog= */ true);
}
@@ -69,10 +80,10 @@ class DisplayTopology {
* @param displayId The logical display ID
*/
void removeDisplay(int displayId) {
- if (!isDisplayPresent(displayId, mRoot)) {
+ if (findDisplay(displayId, mRoot) == null) {
return;
}
- Queue<TreeNode> queue = new LinkedList<>();
+ Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(mRoot);
mRoot = null;
while (!queue.isEmpty()) {
@@ -115,7 +126,11 @@ class DisplayTopology {
}
}
- private void addDisplay(int displayId, double width, double height, boolean shouldLog) {
+ private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
+ if (findDisplay(displayId, mRoot) != null) {
+ throw new IllegalArgumentException(
+ "DisplayTopology: attempting to add a display that already exists");
+ }
if (mRoot == null) {
mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
mPrimaryDisplayId = displayId;
@@ -124,9 +139,8 @@ class DisplayTopology {
}
} else if (mRoot.mChildren.isEmpty()) {
// This is the 2nd display. Align the middles of the top and bottom edges.
- double offset = mRoot.mWidth / 2 - width / 2;
- TreeNode display = new TreeNode(displayId, width, height,
- TreeNode.Position.POSITION_TOP, offset);
+ float offset = mRoot.mWidth / 2 - width / 2;
+ TreeNode display = new TreeNode(displayId, width, height, POSITION_TOP, offset);
mRoot.mChildren.add(display);
if (shouldLog) {
Slog.i(TAG, "Second display added: " + display + ", parent ID: "
@@ -134,8 +148,8 @@ class DisplayTopology {
}
} else {
TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first;
- TreeNode newDisplay = new TreeNode(displayId, width, height,
- TreeNode.Position.POSITION_RIGHT, /* offset= */ 0);
+ TreeNode newDisplay = new TreeNode(displayId, width, height, POSITION_RIGHT,
+ /* offset= */ 0);
rightMostDisplay.mChildren.add(newDisplay);
if (shouldLog) {
Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: "
@@ -150,11 +164,11 @@ class DisplayTopology {
* @return The display that is the furthest to the right and the x position of the right edge
* of that display
*/
- private Pair<TreeNode, Double> findRightMostDisplay(TreeNode display, double xPos) {
- Pair<TreeNode, Double> result = new Pair<>(display, xPos);
+ private static Pair<TreeNode, Float> findRightMostDisplay(TreeNode display, float xPos) {
+ Pair<TreeNode, Float> result = new Pair<>(display, xPos);
for (TreeNode child : display.mChildren) {
// The x position of the right edge of the child
- double childXPos;
+ float childXPos;
switch (child.mPosition) {
case POSITION_LEFT -> childXPos = xPos - display.mWidth;
case POSITION_TOP, POSITION_BOTTOM ->
@@ -164,7 +178,7 @@ class DisplayTopology {
}
// Recursive call - find the rightmost display starting from the child
- Pair<TreeNode, Double> childResult = findRightMostDisplay(child, childXPos);
+ Pair<TreeNode, Float> childResult = findRightMostDisplay(child, childXPos);
// Check if the one found is further right
if (childResult.second > result.second) {
result = new Pair<>(childResult.first, childResult.second);
@@ -173,19 +187,200 @@ class DisplayTopology {
return result;
}
- private boolean isDisplayPresent(int displayId, TreeNode node) {
- if (node == null) {
- return false;
+ @Nullable
+ private static TreeNode findDisplay(int displayId, TreeNode startingNode) {
+ if (startingNode == null) {
+ return null;
}
- if (node.mDisplayId == displayId) {
- return true;
+ if (startingNode.mDisplayId == displayId) {
+ return startingNode;
}
- for (TreeNode child : node.mChildren) {
- if (isDisplayPresent(displayId, child)) {
- return true;
+ for (TreeNode child : startingNode.mChildren) {
+ TreeNode display = findDisplay(displayId, child);
+ if (display != null) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get information about the topology that will be used for the normalization algorithm.
+ * Assigns origins to each display to compute the bounds.
+ * @param bounds The map where the bounds of each display will be put
+ * @param depths The map where the depths of each display in the tree will be put
+ * @param parents The map where the parent of each display will be put
+ * @param display The starting node
+ * @param x The starting x position
+ * @param y The starting y position
+ * @param depth The starting depth
+ */
+ private static void getInfo(Map<TreeNode, RectF> bounds, Map<TreeNode, Integer> depths,
+ Map<TreeNode, TreeNode> parents, TreeNode display, float x, float y, int depth) {
+ bounds.put(display, new RectF(x, y, x + display.mWidth, y + display.mHeight));
+ depths.put(display, depth);
+ for (TreeNode child : display.mChildren) {
+ parents.put(child, display);
+ if (child.mPosition == POSITION_LEFT) {
+ getInfo(bounds, depths, parents, child, x - child.mWidth, y + child.mOffset,
+ depth + 1);
+ } else if (child.mPosition == POSITION_RIGHT) {
+ getInfo(bounds, depths, parents, child, x + display.mWidth, y + child.mOffset,
+ depth + 1);
+ } else if (child.mPosition == POSITION_TOP) {
+ getInfo(bounds, depths, parents, child, x + child.mOffset, y - child.mHeight,
+ depth + 1);
+ } else if (child.mPosition == POSITION_BOTTOM) {
+ getInfo(bounds, depths, parents, child, x + child.mOffset, y + display.mHeight,
+ depth + 1);
+ }
+ }
+ }
+
+ /**
+ * Update the topology to remove any overlaps between displays.
+ */
+ @VisibleForTesting
+ void normalize() {
+ if (mRoot == null) {
+ return;
+ }
+ Map<TreeNode, RectF> bounds = new HashMap<>();
+ Map<TreeNode, Integer> depths = new HashMap<>();
+ Map<TreeNode, TreeNode> parents = new HashMap<>();
+ getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
+
+ // Sort the displays first by their depth in the tree, then by the distance of their top
+ // left point from the root display's origin (0, 0). This way we process the displays
+ // starting at the root and we push out a display if necessary.
+ Comparator<TreeNode> comparator = (d1, d2) -> {
+ if (d1 == d2) {
+ return 0;
+ }
+
+ int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
+ if (compareDepths != 0) {
+ return compareDepths;
+ }
+
+ RectF bounds1 = bounds.get(d1);
+ RectF bounds2 = bounds.get(d2);
+ return Double.compare(Math.hypot(bounds1.left, bounds1.top),
+ Math.hypot(bounds2.left, bounds2.top));
+ };
+ List<TreeNode> displays = new ArrayList<>(bounds.keySet());
+ displays.sort(comparator);
+
+ for (int i = 1; i < displays.size(); i++) {
+ TreeNode targetDisplay = displays.get(i);
+ TreeNode lastIntersectingSourceDisplay = null;
+ float lastOffsetX = 0;
+ float lastOffsetY = 0;
+
+ for (int j = 0; j < i; j++) {
+ TreeNode sourceDisplay = displays.get(j);
+ RectF sourceBounds = bounds.get(sourceDisplay);
+ RectF targetBounds = bounds.get(targetDisplay);
+
+ if (!RectF.intersects(sourceBounds, targetBounds)) {
+ continue;
+ }
+
+ // Find the offset by which to move the display. Pick the smaller one among the x
+ // and y axes.
+ float offsetX = targetBounds.left >= 0
+ ? sourceBounds.right - targetBounds.left
+ : sourceBounds.left - targetBounds.right;
+ float offsetY = targetBounds.top >= 0
+ ? sourceBounds.bottom - targetBounds.top
+ : sourceBounds.top - targetBounds.bottom;
+ if (Math.abs(offsetX) <= Math.abs(offsetY)) {
+ targetBounds.left += offsetX;
+ targetBounds.right += offsetX;
+ // We need to also update the offset in the tree
+ if (targetDisplay.mPosition == POSITION_TOP
+ || targetDisplay.mPosition == POSITION_BOTTOM) {
+ targetDisplay.mOffset += offsetX;
+ }
+ offsetY = 0;
+ } else {
+ targetBounds.top += offsetY;
+ targetBounds.bottom += offsetY;
+ // We need to also update the offset in the tree
+ if (targetDisplay.mPosition == POSITION_LEFT
+ || targetDisplay.mPosition == POSITION_RIGHT) {
+ targetDisplay.mOffset += offsetY;
+ }
+ offsetX = 0;
+ }
+
+ lastIntersectingSourceDisplay = sourceDisplay;
+ lastOffsetX = offsetX;
+ lastOffsetY = offsetY;
+ }
+
+ // Now re-parent the target display to the last intersecting source display if it no
+ // longer touches its parent.
+ if (lastIntersectingSourceDisplay == null) {
+ // There was no overlap.
+ continue;
+ }
+ TreeNode parent = parents.get(targetDisplay);
+ if (parent == lastIntersectingSourceDisplay) {
+ // The displays are moved in such a way that they're adjacent to the intersecting
+ // display. If the last intersecting display happens to be the parent then we
+ // already know that the display is adjacent to its parent.
+ continue;
+ }
+
+ RectF childBounds = bounds.get(targetDisplay);
+ RectF parentBounds = bounds.get(parent);
+ // Check that the edges are on the same line
+ boolean areTouching = switch (targetDisplay.mPosition) {
+ case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
+ case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
+ case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
+ case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
+ };
+ // Check that the offset is within bounds
+ areTouching &= switch (targetDisplay.mPosition) {
+ case POSITION_LEFT, POSITION_RIGHT ->
+ childBounds.bottom + EPSILON >= parentBounds.top
+ && childBounds.top <= parentBounds.bottom + EPSILON;
+ case POSITION_TOP, POSITION_BOTTOM ->
+ childBounds.right + EPSILON >= parentBounds.left
+ && childBounds.left <= parentBounds.right + EPSILON;
+ };
+
+ if (!areTouching) {
+ // Re-parent the display.
+ parent.mChildren.remove(targetDisplay);
+ RectF lastIntersectingSourceDisplayBounds =
+ bounds.get(lastIntersectingSourceDisplay);
+ lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
+
+ if (lastOffsetX != 0) {
+ targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
+ targetDisplay.mOffset =
+ childBounds.top - lastIntersectingSourceDisplayBounds.top;
+ } else if (lastOffsetY != 0) {
+ targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
+ targetDisplay.mOffset =
+ childBounds.left - lastIntersectingSourceDisplayBounds.left;
+ }
}
}
- return false;
+ }
+
+ /**
+ * Tests whether two brightness float values are within a small enough tolerance
+ * of each other.
+ * @param a first float to compare
+ * @param b second float to compare
+ * @return whether the two values are within a small enough tolerance value
+ */
+ public static boolean floatEquals(float a, float b) {
+ return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON;
}
@VisibleForTesting
@@ -201,13 +396,13 @@ class DisplayTopology {
* The width of the display in density-independent pixels (dp).
*/
@VisibleForTesting
- double mWidth;
+ float mWidth;
/**
* The height of the display in density-independent pixels (dp).
*/
@VisibleForTesting
- double mHeight;
+ float mHeight;
/**
* The position of this display relative to its parent.
@@ -222,13 +417,12 @@ class DisplayTopology {
* used is density-independent pixels (dp).
*/
@VisibleForTesting
- double mOffset;
+ float mOffset;
@VisibleForTesting
final List<TreeNode> mChildren = new ArrayList<>();
- TreeNode(int displayId, double width, double height, Position position,
- double offset) {
+ TreeNode(int displayId, float width, float height, Position position, float offset) {
mDisplayId = displayId;
mWidth = width;
mHeight = height;
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 46358dfd90ec..b101e5893b97 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -89,8 +89,8 @@ class DisplayTopologyCoordinator {
* @param info The display info
* @return The width of the display in dp
*/
- private double getWidth(DisplayInfo info) {
- return info.logicalWidth * (double) DisplayMetrics.DENSITY_DEFAULT
+ private float getWidth(DisplayInfo info) {
+ return info.logicalWidth * (float) DisplayMetrics.DENSITY_DEFAULT
/ info.logicalDensityDpi;
}
@@ -98,8 +98,8 @@ class DisplayTopologyCoordinator {
* @param info The display info
* @return The height of the display in dp
*/
- private double getHeight(DisplayInfo info) {
- return info.logicalHeight * (double) DisplayMetrics.DENSITY_DEFAULT
+ private float getHeight(DisplayInfo info) {
+ return info.logicalHeight * (float) DisplayMetrics.DENSITY_DEFAULT
/ info.logicalDensityDpi;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d0ad6fc0854f..b696c5481205 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -444,14 +444,62 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
new Runnable() {
@Override
public void run() {
- if (!isActiveSource()) {
+ if (isActiveSource()) {
+ return;
+ }
+
+ if (getActiveSource().logicalAddress != Constants.ADDR_TV) {
startHdmiCecActiveSourceLostActivity();
mDelayedStandbyOnActiveSourceLostHandler
.removeCallbacksAndMessages(null);
mDelayedStandbyOnActiveSourceLostHandler.postDelayed(
new DelayedStandbyOnActiveSourceLostRunnable(),
STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ return;
}
+
+ // We observed specific TV panels (old models) that send faulty CEC
+ // source changing messages, especially during wake-up.
+ // This request helps to check if the TV correctly asserted active
+ // source or not. If the request times out, active source is
+ // asserted by the local device.
+ addAndStartAction(new RequestActiveSourceAction(mService.playback(),
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ // If a device answers to <Request Active Source>, the
+ // pop-up should be triggered.
+ // During this action, the TV can switch to an HDMI input
+ // with a non-CEC capable device that won't be able to
+ // answer this request.
+ // In this case, the known active source would be
+ // represented by a valid physical address, but invalid
+ // logical address. The pop-up will be shown and the local
+ // device will not assert active source.
+ if (result == HdmiControlManager.RESULT_SUCCESS
+ || getActiveSource().logicalAddress
+ != Constants.ADDR_TV) {
+ startHdmiCecActiveSourceLostActivity();
+ mDelayedStandbyOnActiveSourceLostHandler
+ .removeCallbacksAndMessages(null);
+ mDelayedStandbyOnActiveSourceLostHandler
+ .postDelayed(
+ new DelayedStandbyOnActiveSourceLostRunnable(),
+ STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ } else {
+ // The request times out and the local device is not
+ // active source, but the TV previously asserted active
+ // source.
+ if (getActiveSource().logicalAddress
+ == Constants.ADDR_TV) {
+ mService.setAndBroadcastActiveSource(
+ mService.getPhysicalAddress(),
+ getDeviceInfo().getDeviceType(),
+ Constants.ADDR_BROADCAST,
+ "RequestActiveSourceAction#RESULT_TIMEOUT");
+ }
+ }
+ }}));
}
}, POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
return;
@@ -698,6 +746,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
removeAction(HotplugDetectionAction.class);
removeAction(NewDeviceAction.class);
removeAction(PowerStatusMonitorActionFromPlayback.class);
+ removeAction(RequestActiveSourceAction.class);
super.disableDevice(initiatedByCec, callback);
clearDeviceInfoList();
checkIfPendingActionsCleared();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index aae7b59b1a1a..5682c330e4b0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -471,6 +471,10 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
assertRunOnServiceThread();
if (oldPath == newPath) {
+ HdmiCecMessage setStreamPath =
+ HdmiCecMessageBuilder.buildSetStreamPath(getDeviceInfo().getLogicalAddress(),
+ oldPath);
+ mService.sendCecCommand(setStreamPath);
return;
}
HdmiCecMessage routingChange =
@@ -642,7 +646,8 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
int address = message.getSource();
int type = message.getParams()[2];
- if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
+ if (!ActiveSource.of(address, path).equals(getActiveSource())) {
+ HdmiLogger.debug("Check if a new device is connected to the active path");
handleNewDeviceAtTheTailOfActivePath(path);
}
startNewDeviceAction(ActiveSource.of(address, path), type);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 81be0baefd7a..cb0b4b08db8f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4344,6 +4344,7 @@ public class HdmiControlService extends SystemService {
if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
HdmiCecLocalDevicePlayback playback = playback();
playback.dismissUiOnActiveSourceStatusRecovered();
+ playback.removeAction(RequestActiveSourceAction.class);
playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
caller);
playback.wakeUpIfActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index a33d70a9b876..b0e93989d463 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -22,11 +22,11 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
/**
- * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
- * panels.
- * This action has a delay before sending <Request Active Source>. This is because it should wait
- * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
- * received or the TV switched to another input.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ *
+ * For TV panels, this action has a delay before sending <Request Active Source>. This is because it
+ * should wait for a possible request from LauncherX and can be cancelled if an <Active Source>
+ * message was received or the TV switched to another input.
*/
public class RequestActiveSourceAction extends HdmiCecFeatureAction {
private static final String TAG = "RequestActiveSourceAction";
@@ -55,6 +55,13 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction {
boolean start() {
Slog.v(TAG, "RequestActiveSourceAction started.");
+ if (localDevice().mService.isPlaybackDevice()) {
+ mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
+ sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ return true;
+ }
+
mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
// We wait for default timeout to allow the message triggered by the LauncherX API call to
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/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
index 595a035baf1d..408df5a784ff 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -19,8 +19,7 @@ package com.android.server.integrity.parser;
import android.annotation.Nullable;
/**
- * A wrapper class to represent an indexing range that is identified by the {@link
- * RuleIndexingController}.
+ * A wrapper class to represent an indexing range.
*/
public class RuleIndexRange {
private int mStartIndex;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
deleted file mode 100644
index 348a03be7317..000000000000
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
-
-import android.content.integrity.AppInstallMetadata;
-
-import com.android.server.integrity.model.BitInputStream;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/** Helper class to identify the necessary indexes that needs to be read. */
-public class RuleIndexingController {
-
- private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes;
- private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes;
- private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes;
-
- /**
- * Provide the indexing file to read and the object will be constructed by reading and
- * identifying the indexes.
- */
- public RuleIndexingController(InputStream inputStream) throws IOException {
- BitInputStream bitInputStream = new BitInputStream(inputStream);
- sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
- sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
- sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
- }
-
- /**
- * Returns a list of integers with the starting and ending bytes of the rules that needs to be
- * read and evaluated.
- */
- public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
- List<RuleIndexRange> indexRanges = new ArrayList<>();
-
- // Add the range for package name indexes rules.
- indexRanges.add(
- searchIndexingKeysRangeContainingKey(
- sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
-
- // Add the range for app certificate indexes rules of all certificates.
- for (String appCertificate : appInstallMetadata.getAppCertificates()) {
- indexRanges.add(
- searchIndexingKeysRangeContainingKey(
- sAppCertificateBasedIndexes, appCertificate));
- }
-
- // Add the range for unindexed rules.
- indexRanges.add(
- new RuleIndexRange(
- sUnindexedRuleIndexes.get(START_INDEXING_KEY),
- sUnindexedRuleIndexes.get(END_INDEXING_KEY)));
-
- return indexRanges;
- }
-
- private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
- throws IOException {
- LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>();
- while (bitInputStream.hasNext()) {
- String key = getStringValue(bitInputStream);
- int value = getIntValue(bitInputStream);
-
- keyToIndexMap.put(key, value);
-
- if (key.matches(END_INDEXING_KEY)) {
- break;
- }
- }
- if (keyToIndexMap.size() < 2) {
- throw new IllegalStateException("Indexing file is corrupt.");
- }
- return keyToIndexMap;
- }
-
- private static RuleIndexRange searchIndexingKeysRangeContainingKey(
- LinkedHashMap<String, Integer> indexMap, String searchedKey) {
- List<String> keys = indexMap.keySet().stream().collect(Collectors.toList());
- List<String> identifiedKeyRange =
- searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1);
- return new RuleIndexRange(
- indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1)));
- }
-
- private static List<String> searchKeysRangeContainingKey(
- List<String> sortedKeyList, String key, int startIndex, int endIndex) {
- if (endIndex <= startIndex) {
- throw new IllegalStateException("Indexing file is corrupt.");
- }
- if (endIndex - startIndex == 1) {
- return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
- }
-
- int midKeyIndex = startIndex + ((endIndex - startIndex) / 2);
- String midKey = sortedKeyList.get(midKeyIndex);
-
- if (key.compareTo(midKey) >= 0) {
- return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex);
- } else {
- return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex);
- }
- }
-}
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/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/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index c849a37ede53..258a87eae196 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -156,7 +156,7 @@ class DragDropController {
SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId,
float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
+ Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=0x" +
Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
+ touchY + ") thumb center(" + thumbCenterX + "," + thumbCenterY + ")");
}
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 ed56382450e9..6baab25d0dce 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1399,6 +1399,10 @@ 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);
@@ -1768,13 +1772,6 @@ 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/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 17af6335e25b..85e73561cf59 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -21,7 +21,7 @@ import android.view.Display
import android.view.DisplayInfo
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentMatchers.anyDouble
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -55,9 +55,9 @@ class DisplayTopologyCoordinatorTest {
coordinator.onDisplayAdded(displayInfo)
- val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toDouble()
+ val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
/ displayInfo.logicalDensityDpi)
- val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toDouble()
+ val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
/ displayInfo.logicalDensityDpi)
verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
}
@@ -68,7 +68,7 @@ class DisplayTopologyCoordinatorTest {
coordinator.onDisplayAdded(displayInfo)
- verify(mockTopology, never()).addDisplay(anyInt(), anyDouble(), anyDouble())
+ verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
}
@Test
@@ -78,6 +78,6 @@ class DisplayTopologyCoordinatorTest {
coordinator.onDisplayAdded(displayInfo)
- verify(mockTopology, never()).addDisplay(anyInt(), anyDouble(), anyDouble())
+ verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
}
} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
index f3a8d8415c19..cd8c26d0d337 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
@@ -17,6 +17,9 @@
package com.android.server.display
import android.view.Display
+import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM
+import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP
+import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -26,8 +29,8 @@ class DisplayTopologyTest {
@Test
fun addOneDisplay() {
val displayId = 1
- val width = 800.0
- val height = 600.0
+ val width = 800f
+ val height = 600f
topology.addDisplay(displayId, width, height)
@@ -43,12 +46,12 @@ class DisplayTopologyTest {
@Test
fun addTwoDisplays() {
val displayId1 = 1
- val width1 = 800.0
- val height1 = 600.0
+ val width1 = 800f
+ val height1 = 600f
val displayId2 = 2
- val width2 = 1000.0
- val height2 = 1500.0
+ val width2 = 1000f
+ val height2 = 1500f
topology.addDisplay(displayId1, width1, height1)
topology.addDisplay(displayId2, width2, height2)
@@ -66,20 +69,19 @@ class DisplayTopologyTest {
assertThat(display2.mWidth).isEqualTo(width2)
assertThat(display2.mHeight).isEqualTo(height2)
assertThat(display2.mChildren).isEmpty()
- assertThat(display2.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_TOP)
+ assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
}
@Test
fun addManyDisplays() {
val displayId1 = 1
- val width1 = 800.0
- val height1 = 600.0
+ val width1 = 800f
+ val height1 = 600f
val displayId2 = 2
- val width2 = 1000.0
- val height2 = 1500.0
+ val width2 = 1000f
+ val height2 = 1500f
topology.addDisplay(displayId1, width1, height1)
topology.addDisplay(displayId2, width2, height2)
@@ -102,8 +104,7 @@ class DisplayTopologyTest {
assertThat(display2.mWidth).isEqualTo(width2)
assertThat(display2.mHeight).isEqualTo(height2)
assertThat(display2.mChildren).hasSize(1)
- assertThat(display2.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_TOP)
+ assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
var display = display2
@@ -114,8 +115,7 @@ class DisplayTopologyTest {
assertThat(display.mHeight).isEqualTo(height1)
// The last display should have no children
assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+ assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
assertThat(display.mOffset).isEqualTo(0)
}
}
@@ -123,12 +123,12 @@ class DisplayTopologyTest {
@Test
fun removeDisplays() {
val displayId1 = 1
- val width1 = 800.0
- val height1 = 600.0
+ val width1 = 800f
+ val height1 = 600f
val displayId2 = 2
- val width2 = 1000.0
- val height2 = 1500.0
+ val width2 = 1000f
+ val height2 = 1500f
topology.addDisplay(displayId1, width1, height1)
topology.addDisplay(displayId2, width2, height2)
@@ -154,8 +154,7 @@ class DisplayTopologyTest {
assertThat(display2.mWidth).isEqualTo(width2)
assertThat(display2.mHeight).isEqualTo(height2)
assertThat(display2.mChildren).hasSize(1)
- assertThat(display2.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_TOP)
+ assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
var display = display2
@@ -169,8 +168,7 @@ class DisplayTopologyTest {
assertThat(display.mHeight).isEqualTo(height1)
// The last display should have no children
assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+ assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
assertThat(display.mOffset).isEqualTo(0)
}
@@ -194,8 +192,7 @@ class DisplayTopologyTest {
assertThat(display2.mWidth).isEqualTo(width2)
assertThat(display2.mHeight).isEqualTo(height2)
assertThat(display2.mChildren).hasSize(1)
- assertThat(display2.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_TOP)
+ assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
display = display2
@@ -209,8 +206,7 @@ class DisplayTopologyTest {
assertThat(display.mHeight).isEqualTo(height1)
// The last display should have no children
assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.mPosition).isEqualTo(
- DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+ assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
assertThat(display.mOffset).isEqualTo(0)
}
}
@@ -218,8 +214,8 @@ class DisplayTopologyTest {
@Test
fun removeAllDisplays() {
val displayId = 1
- val width = 800.0
- val height = 600.0
+ val width = 800f
+ val height = 600f
topology.addDisplay(displayId, width, height)
topology.removeDisplay(displayId)
@@ -231,8 +227,8 @@ class DisplayTopologyTest {
@Test
fun removeDisplayThatDoesNotExist() {
val displayId = 1
- val width = 800.0
- val height = 600.0
+ val width = 800f
+ val height = 600f
topology.addDisplay(displayId, width, height)
topology.removeDisplay(3)
@@ -245,4 +241,236 @@ class DisplayTopologyTest {
assertThat(display.mHeight).isEqualTo(height)
assertThat(display.mChildren).isEmpty()
}
+
+ @Test
+ fun removePrimaryDisplay() {
+ val displayId1 = 1
+ val displayId2 = 2
+ val width = 800f
+ val height = 600f
+
+ topology.addDisplay(displayId1, width, height)
+ topology.addDisplay(displayId2, width, height)
+ topology.mPrimaryDisplayId = displayId2
+ topology.removeDisplay(displayId2)
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
+ val display = topology.mRoot!!
+ assertThat(display.mDisplayId).isEqualTo(displayId1)
+ assertThat(display.mWidth).isEqualTo(width)
+ assertThat(display.mHeight).isEqualTo(height)
+ assertThat(display.mChildren).isEmpty()
+ }
+
+ @Test
+ fun normalization_noOverlaps_leavesTopologyUnchanged() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
+ topology.mRoot = display1
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.mChildren.add(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+ display1.mChildren.add(display3)
+ topology.mPrimaryDisplayId = primaryDisplayId
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.mChildren.add(display4)
+
+ topology.normalize()
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.mRoot!!
+ assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+ assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay1.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay1.mChildren).hasSize(2)
+
+ val actualDisplay2 = actualDisplay1.mChildren[0]
+ assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+ assertThat(actualDisplay2.mWidth).isEqualTo(600f)
+ assertThat(actualDisplay2.mHeight).isEqualTo(200f)
+ assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+ assertThat(actualDisplay2.mChildren).hasSize(1)
+
+ val actualDisplay3 = actualDisplay1.mChildren[1]
+ assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+ assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+ assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+ assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.mOffset).isEqualTo(400f)
+ assertThat(actualDisplay3.mChildren).isEmpty()
+
+ val actualDisplay4 = actualDisplay2.mChildren[0]
+ assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
+ assertThat(actualDisplay4.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay4.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.mOffset).isEqualTo(0f)
+ assertThat(actualDisplay4.mChildren).isEmpty()
+ }
+
+ @Test
+ fun normalization_moveDisplayWithoutReparenting() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
+ topology.mRoot = display1
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.mChildren.add(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+ display1.mChildren.add(display3)
+ topology.mPrimaryDisplayId = primaryDisplayId
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.mChildren.add(display4)
+
+ // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent.
+ topology.normalize()
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.mRoot!!
+ assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+ assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay1.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay1.mChildren).hasSize(1)
+
+ val actualDisplay2 = actualDisplay1.mChildren[0]
+ assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+ assertThat(actualDisplay2.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay2.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+ assertThat(actualDisplay2.mChildren).hasSize(2)
+
+ val actualDisplay3 = actualDisplay2.mChildren[1]
+ assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+ assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+ assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+ assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.mOffset).isEqualTo(10f)
+ assertThat(actualDisplay3.mChildren).isEmpty()
+
+ val actualDisplay4 = actualDisplay2.mChildren[0]
+ assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
+ assertThat(actualDisplay4.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay4.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.mOffset).isEqualTo(210f)
+ assertThat(actualDisplay4.mChildren).isEmpty()
+ }
+
+ @Test
+ fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 50f, /* position= */ null, /* offset= */ 0f)
+ topology.mRoot = display1
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.mChildren.add(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+ display1.mChildren.add(display3)
+ topology.mPrimaryDisplayId = primaryDisplayId
+
+ // Display 3 gets moved and its left side is still on the same line as the right side
+ // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2
+ // becomes its new parent.
+ topology.normalize()
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.mRoot!!
+ assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+ assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay1.mHeight).isEqualTo(50f)
+ assertThat(actualDisplay1.mChildren).hasSize(1)
+
+ val actualDisplay2 = actualDisplay1.mChildren[0]
+ assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+ assertThat(actualDisplay2.mWidth).isEqualTo(600f)
+ assertThat(actualDisplay2.mHeight).isEqualTo(200f)
+ assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+ assertThat(actualDisplay2.mChildren).hasSize(1)
+
+ val actualDisplay3 = actualDisplay2.mChildren[0]
+ assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+ assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+ assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+ assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_BOTTOM)
+ assertThat(actualDisplay3.mOffset).isEqualTo(0f)
+ assertThat(actualDisplay3.mChildren).isEmpty()
+ }
+
+ @Test
+ fun normalization_moveAndReparentDisplay() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
+ topology.mRoot = display1
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.mChildren.add(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+ display1.mChildren.add(display3)
+ topology.mPrimaryDisplayId = primaryDisplayId
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.mChildren.add(display4)
+
+ topology.normalize()
+
+ assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.mRoot!!
+ assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+ assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay1.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay1.mChildren).hasSize(1)
+
+ val actualDisplay2 = actualDisplay1.mChildren[0]
+ assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+ assertThat(actualDisplay2.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay2.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+ assertThat(actualDisplay2.mChildren).hasSize(1)
+
+ val actualDisplay3 = actualDisplay2.mChildren[0]
+ assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+ assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+ assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+ assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.mOffset).isEqualTo(400f)
+ assertThat(actualDisplay3.mChildren).hasSize(1)
+
+ val actualDisplay4 = actualDisplay3.mChildren[0]
+ assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
+ assertThat(actualDisplay4.mWidth).isEqualTo(200f)
+ assertThat(actualDisplay4.mHeight).isEqualTo(600f)
+ assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.mOffset).isEqualTo(-400f)
+ assertThat(actualDisplay4.mChildren).isEmpty()
+ }
} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index a0005d968e31..3360e1d08bda 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -413,7 +413,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -422,9 +423,8 @@ public class HdmiCecLocalDevicePlaybackTest {
int newPlaybackPhysicalAddress = 0x2100;
int switchPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(newPlaybackPhysicalAddress);
- mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
-
+ mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
@@ -457,7 +457,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -617,7 +618,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -722,7 +724,8 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
ADDR_INVALID);
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS,true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -1265,14 +1268,35 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
-
- // After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ // After 30s of device inactivity, device would assert active source.
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
}
@Test
+ public void handleActiveSourceFromTv_tvNotAnswerRequest_assertActiveSource() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ mPowerManager.setInteractive(true);
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
+ message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+ // After 30s of device inactivity, device would assert active source.
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ false);
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ }
+
+ @Test
public void handleActiveSource_otherDevice_ActiveSource_mediaSessionsPaused() {
mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
@@ -1343,7 +1367,8 @@ public class HdmiCecLocalDevicePlaybackTest {
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
// 3. DUT becomes <AS> again.
@@ -1704,9 +1729,9 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
.isEqualTo(Constants.HANDLED);
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
-
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+ false);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@@ -2323,11 +2348,197 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
// After 30s of device inactivity, device would go to sleep.
- skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+ true);
assertThat(mPowerManager.isInteractive()).isFalse();
}
@Test
+ public void onActiveSourceLostToTv_requestActiveSourceAnsweredFromTv_showPopup() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ }
+
+ @Test
+ public void onActiveSourceLostToTv_requestActiveSourceNotAnswered_assertActiveSource() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is not shown, playback device asserts active source since TV doesn't answer the
+ // request.
+ assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(mPlaybackLogicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(mPlaybackPhysicalAddress);
+ }
+
+ @Test
+ public void onActiveSourceLost_requestActiveSourceNotAnswered_playbackIsAS_dontShowPopup() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+ HdmiCecMessage setStreamPathToPlayback = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+ mPlaybackPhysicalAddress);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is not shown since playback device is active source.
+ assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(mPlaybackLogicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(mPlaybackPhysicalAddress);
+ }
+
+ @Test
+ public void onActiveSourceLost_requestASNotAnswered_setStreamPathToNonCecInput_dontShowPopup() {
+ int otherPhysicalAddress = mPlaybackPhysicalAddress + 0x0100;
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+ HdmiCecMessage setStreamPathToOtherInput = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+ otherPhysicalAddress);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction is triggered.
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToOtherInput))
+ .isEqualTo(Constants.HANDLED);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is shown, playback device doesn't assert active source since active path is
+ // switched to a non-CEC device.
+ assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(ADDR_INVALID);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(otherPhysicalAddress);
+ }
+
+ @Test
public void onActiveSourceLost_interactionWithDut_noStandbyAfterTimeout() {
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -2350,7 +2561,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
// User interacted with the DUT, so the device will not go to standby.
- skipActiveSourceLostUi(0);
+ skipActiveSourceLostUi(0, true, true);
assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
@@ -2387,6 +2598,10 @@ public class HdmiCecLocalDevicePlaybackTest {
// Pop-up is triggered.
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
+ // RequestActiveSourceAction is triggered and TV confirms active source.
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.dispatchAll();
+
assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
@@ -2407,6 +2622,8 @@ public class HdmiCecLocalDevicePlaybackTest {
@Test
public void onActiveSourceLost_incomingRoutingChangeToDut_noStandbyAfterTimeout() {
+ int otherPlaybackLogicalAddress = mPlaybackLogicalAddress == Constants.ADDR_PLAYBACK_2
+ ? Constants.ADDR_PLAYBACK_1 : Constants.ADDR_PLAYBACK_2;
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
@@ -2420,18 +2637,21 @@ public class HdmiCecLocalDevicePlaybackTest {
mNativeWrapper.clearResultMessages();
mTestLooper.dispatchAll();
- HdmiCecMessage activeSourceFromTv =
- HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage activeSourceFromOtherPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(otherPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress + 0x0100);
HdmiCecMessage activeSourceFromPlayback =
HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
mPlaybackPhysicalAddress);
HdmiCecMessage routingChangeToPlayback =
- HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV,
+ mPlaybackPhysicalAddress + 0x0100,
mPlaybackPhysicalAddress);
- assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromOtherPlayback))
.isEqualTo(Constants.HANDLED);
- assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ otherPlaybackLogicalAddress);
mTestLooper.dispatchAll();
// Pop-up is triggered.
@@ -2600,13 +2820,30 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mPowerManager.isInteractive()).isFalse();
}
- private void skipActiveSourceLostUi(long idleDuration) {
+ private void skipActiveSourceLostUi(long idleDuration, boolean activeSourceLostToTv,
+ boolean tvAnswersRequest) {
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
+ if (activeSourceLostToTv) {
+ // RequestActiveSourceAction is triggered.
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ if (tvAnswersRequest) {
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV,
+ 0x0000);
+ mNativeWrapper.onCecMessage(activeSource);
+ mTestLooper.dispatchAll();
+ } else {
+ mTestLooper.moveTimeForward(TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ return;
+ }
+ }
assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
mPowerManagerInternal.setIdleDuration(idleDuration);
mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
}
-}
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 935c8b8720fb..51276a4db883 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2234,6 +2234,25 @@ public class HdmiCecLocalDeviceTvTest {
}
@Test
+ public void handleReportPhysicalAddress_samePathAsActiveSource_differentLA_newActiveSource() {
+ // This scenario can be reproduced if active source is hotplugged out and replaced with
+ // another device that might have another LA.
+ int physicalAddress = 0x1000;
+ mHdmiControlService.setActiveSource(Constants.ADDR_PLAYBACK_1, physicalAddress,
+ "HdmiControlServiceTest");
+ mHdmiCecLocalDeviceTv.setActivePath(physicalAddress);
+ HdmiCecMessage reportPhysicalAddressFromPlayback2 =
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(ADDR_PLAYBACK_2,
+ physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+ physicalAddress);
+ mNativeWrapper.onCecMessage(reportPhysicalAddressFromPlayback2);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+ }
+
+ @Test
public void onOneTouchPlay_wakeUp_addCecDevice() {
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
.isEmpty();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
deleted file mode 100644
index 370bd80003bd..000000000000
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.integrity.AppInstallMetadata;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class RuleIndexingControllerTest {
-
- @Test
- public void verifyIndexRangeSearchIsCorrect() throws IOException {
- InputStream inputStream = obtainDefaultIndexingMapForTest();
-
- RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
- AppInstallMetadata appInstallMetadata =
- new AppInstallMetadata.Builder()
- .setPackageName("ddd")
- .setAppCertificates(Collections.singletonList("777"))
- .setAppCertificateLineage(Collections.singletonList("777"))
- .build();
-
- List<RuleIndexRange> resultingIndexes =
- indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
- assertThat(resultingIndexes)
- .containsExactly(
- new RuleIndexRange(200, 300),
- new RuleIndexRange(700, 800),
- new RuleIndexRange(900, 945));
- }
-
- @Test
- public void verifyIndexRangeSearchIsCorrect_multipleAppCertificates() throws IOException {
- InputStream inputStream = obtainDefaultIndexingMapForTest();
-
- RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
- AppInstallMetadata appInstallMetadata =
- new AppInstallMetadata.Builder()
- .setPackageName("ddd")
- .setAppCertificates(Arrays.asList("777", "999"))
- .setAppCertificateLineage(Arrays.asList("777", "999"))
- .build();
-
- List<RuleIndexRange> resultingIndexes =
- indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
- assertThat(resultingIndexes)
- .containsExactly(
- new RuleIndexRange(200, 300),
- new RuleIndexRange(700, 800),
- new RuleIndexRange(800, 900),
- new RuleIndexRange(900, 945));
- }
-
- @Test
- public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
- InputStream inputStream = obtainDefaultIndexingMapForTest();
-
- RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
- AppInstallMetadata appInstallMetadata =
- new AppInstallMetadata.Builder()
- .setPackageName("bbb")
- .setAppCertificates(Collections.singletonList("999"))
- .setAppCertificateLineage(Collections.singletonList("999"))
- .build();
-
- List<RuleIndexRange> resultingIndexes =
- indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
- assertThat(resultingIndexes)
- .containsExactly(
- new RuleIndexRange(100, 200),
- new RuleIndexRange(800, 900),
- new RuleIndexRange(900, 945));
- }
-
- @Test
- public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException {
- InputStream inputStream = obtainDefaultIndexingMapForTest();
-
- RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
- AppInstallMetadata appInstallMetadata =
- new AppInstallMetadata.Builder()
- .setPackageName("ccc")
- .setAppCertificates(Collections.singletonList("444"))
- .setAppCertificateLineage(Collections.singletonList("444"))
- .build();
-
- List<RuleIndexRange> resultingIndexes =
- indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
- assertThat(resultingIndexes)
- .containsExactly(
- new RuleIndexRange(200, 300),
- new RuleIndexRange(700, 800),
- new RuleIndexRange(900, 945));
- }
-
- @Test
- public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException {
- byte[] stringBytes =
- getBytes(
- getKeyValueString(START_INDEXING_KEY, 100)
- + getKeyValueString(END_INDEXING_KEY, 500)
- + getKeyValueString(START_INDEXING_KEY, 500)
- + getKeyValueString(END_INDEXING_KEY, 900)
- + getKeyValueString(START_INDEXING_KEY, 900)
- + getKeyValueString(END_INDEXING_KEY, 945));
- ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
- rule.put(stringBytes);
- InputStream inputStream = new ByteArrayInputStream(rule.array());
-
- RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
- AppInstallMetadata appInstallMetadata =
- new AppInstallMetadata.Builder()
- .setPackageName("ccc")
- .setAppCertificates(Collections.singletonList("444"))
- .setAppCertificateLineage(Collections.singletonList("444"))
- .build();
-
- List<RuleIndexRange> resultingIndexes =
- indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
- assertThat(resultingIndexes)
- .containsExactly(
- new RuleIndexRange(100, 500),
- new RuleIndexRange(500, 900),
- new RuleIndexRange(900, 945));
- }
-
- @Test
- public void verifyIndexingFileIsCorrupt() throws IOException {
- byte[] stringBytes =
- getBytes(
- getKeyValueString(START_INDEXING_KEY, 100)
- + getKeyValueString("ccc", 200)
- + getKeyValueString(END_INDEXING_KEY, 300)
- + getKeyValueString(END_INDEXING_KEY, 900));
- ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
- rule.put(stringBytes);
- InputStream inputStream = new ByteArrayInputStream(rule.array());
-
- assertThrows(IllegalStateException.class,
- () -> new RuleIndexingController(inputStream));
- }
-
- private static InputStream obtainDefaultIndexingMapForTest() {
- byte[] stringBytes =
- getBytes(
- getKeyValueString(START_INDEXING_KEY, 100)
- + getKeyValueString("ccc", 200)
- + getKeyValueString("eee", 300)
- + getKeyValueString("hhh", 400)
- + getKeyValueString(END_INDEXING_KEY, 500)
- + getKeyValueString(START_INDEXING_KEY, 500)
- + getKeyValueString("111", 600)
- + getKeyValueString("444", 700)
- + getKeyValueString("888", 800)
- + getKeyValueString(END_INDEXING_KEY, 900)
- + getKeyValueString(START_INDEXING_KEY, 900)
- + getKeyValueString(END_INDEXING_KEY, 945));
- ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
- rule.put(stringBytes);
- return new ByteArrayInputStream(rule.array());
- }
-
- private static String getKeyValueString(String key, int value) {
- String isNotHashed = "0";
- return isNotHashed
- + getBits(key.length(), VALUE_SIZE_BITS)
- + getValueBits(key)
- + getBits(value, /* numOfBits= */ 32);
- }
-}
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.
*/
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a7fe0cb0940c..fad59f8bb37b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17278,6 +17278,18 @@ public class TelephonyManager {
}
/**
+ * Setup sISms for testing.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static void setupISmsForTest(ISms iSms) {
+ synchronized (sCacheLock) {
+ sISms = iSms;
+ }
+ }
+
+ /**
* Whether device can connect to 5G network when two SIMs are active.
*
* @hide TODO b/153669716: remove or make system API.