summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt4
-rw-r--r--core/api/system-current.txt24
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/AppOpsManager.java3
-rw-r--r--core/java/android/app/ResourcesManager.java3
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java8
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl24
-rw-r--r--core/java/android/content/res/ResourcesImpl.java10
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java11
-rw-r--r--core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java39
-rw-r--r--core/java/android/hardware/biometrics/PromptInfo.java7
-rw-r--r--core/java/android/hardware/biometrics/PromptVerticalListContentView.java48
-rw-r--r--core/java/android/hardware/display/ColorDisplayManager.java7
-rw-r--r--core/java/android/permission/PermissionManager.java3
-rw-r--r--core/java/android/provider/Settings.java26
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java17
-rw-r--r--core/java/android/service/voice/HotwordDetectedResult.java13
-rw-r--r--core/java/android/service/voice/OWNERS1
-rw-r--r--core/java/android/service/voice/VisualQueryAttentionResult.java45
-rw-r--r--core/java/android/service/voice/VisualQueryDetectedResult.java32
-rw-r--r--core/java/android/service/voice/VisualQueryDetectionService.java33
-rw-r--r--core/java/android/service/voice/VisualQueryDetector.java10
-rw-r--r--core/java/android/service/voice/flags/flags.aconfig24
-rw-r--r--core/java/android/text/MeasuredParagraph.java17
-rw-r--r--core/java/android/view/PointerIcon.java53
-rw-r--r--core/java/android/view/ViewRootImpl.java4
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig5
-rw-r--r--core/java/android/window/BackProgressAnimator.java8
-rw-r--r--core/jni/OWNERS3
-rw-r--r--core/proto/android/providers/settings/secure.proto7
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java15
-rw-r--r--core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java6
-rw-r--r--core/tests/coretests/src/android/window/WindowMetricsHelperTest.java6
-rw-r--r--data/etc/privapp-permissions-platform.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java25
-rw-r--r--libs/hwui/Android.bp8
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp122
-rw-r--r--libs/hwui/pipeline/skia/SkiaCpuPipeline.h77
-rw-r--r--libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp185
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp16
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h (renamed from libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h)4
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp234
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h24
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp13
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.h (renamed from libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h)4
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h59
l---------libs/hwui/platform/host/android/api-level.h1
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h83
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h35
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp4
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h3
-rw-r--r--libs/hwui/utils/Color.cpp11
-rw-r--r--libs/hwui/utils/Color.h2
-rw-r--r--libs/input/SpriteController.cpp5
-rw-r--r--media/OWNERS1
-rw-r--r--media/java/android/media/AudioDescriptor.java2
-rw-r--r--media/java/android/media/AudioManager.java2
-rw-r--r--nfc/api/current.txt8
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java50
-rw-r--r--packages/CredentialManager/AndroidManifest.xml1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt8
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java6
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt4
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt4
-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/NotificationStackScrollLayout.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java4
-rw-r--r--packages/SystemUI/tests/utils/src/android/os/PowerManagerKosmos.kt (renamed from libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h)23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt4
-rw-r--r--services/accessibility/accessibility.aconfig5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java34
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java6
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java20
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java28
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java567
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java566
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java3
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java (renamed from services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java)63
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java41
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java41
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java53
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java86
-rw-r--r--services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java6
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java70
-rw-r--r--services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java321
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java620
-rw-r--r--services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java1042
-rw-r--r--services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java14
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java29
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java20
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java67
-rw-r--r--services/core/java/com/android/server/am/BroadcastSkipPolicy.java128
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java14
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java88
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java83
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java12
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java2
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java9
-rw-r--r--services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java (renamed from services/core/java/com/android/server/display/config/LowBrightnessData.java)22
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java12
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java5
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java16
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java8
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java6
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java6
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java12
-rw-r--r--services/core/java/com/android/server/wm/Task.java8
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java8
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java4
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp31
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd4
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt30
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java28
-rw-r--r--services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java7
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java155
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt30
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt86
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java567
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java26
186 files changed, 4438 insertions, 3035 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 93fa9a0e8742..ce66a4f073d3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8028,7 +8028,7 @@ package android.app.admin {
method public CharSequence getDeviceOwnerLockScreenInfo();
method @Nullable public String getDevicePolicyManagementRoleHolderPackage();
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
- method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @NonNull @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES, conditional=true) public String getEnrollmentSpecificId();
+ method @NonNull @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES, conditional=true) public String getEnrollmentSpecificId();
method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET, conditional=true) public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
@@ -8067,7 +8067,7 @@ package android.app.admin {
method @Deprecated public int getPasswordMinimumSymbols(@Nullable android.content.ComponentName);
method @Deprecated public int getPasswordMinimumUpperCase(@Nullable android.content.ComponentName);
method @Deprecated public int getPasswordQuality(@Nullable android.content.ComponentName);
- method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional=true) public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@Nullable android.content.ComponentName);
+ method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional=true) public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@Nullable android.content.ComponentName);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, conditional=true) public int getPermissionGrantState(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
method public int getPermissionPolicy(android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 22d39a4a0fa6..702a47ef54ec 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13483,12 +13483,12 @@ package android.service.voice {
method public static int getMaxBundleSize();
method public static int getMaxHotwordPhraseId();
method public static int getMaxScore();
- method @FlaggedApi("android.service.voice.flags.allow_speaker_id_egress") public static int getMaxSpeakerId();
+ method public static int getMaxSpeakerId();
method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
method public int getPersonalizedScore();
method public int getProximity();
method public int getScore();
- method @FlaggedApi("android.service.voice.flags.allow_speaker_id_egress") public int getSpeakerId();
+ method public int getSpeakerId();
method public boolean isHotwordDetectionPersonalized();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int AUDIO_CHANNEL_UNSET = -1; // 0xffffffff
@@ -13522,7 +13522,7 @@ package android.service.voice {
method @NonNull public android.service.voice.HotwordDetectedResult.Builder setMediaSyncEvent(@NonNull android.media.MediaSyncEvent);
method @NonNull public android.service.voice.HotwordDetectedResult.Builder setPersonalizedScore(int);
method @NonNull public android.service.voice.HotwordDetectedResult.Builder setScore(int);
- method @FlaggedApi("android.service.voice.flags.allow_speaker_id_egress") @NonNull public android.service.voice.HotwordDetectedResult.Builder setSpeakerId(int);
+ method @NonNull public android.service.voice.HotwordDetectedResult.Builder setSpeakerId(int);
}
public abstract class HotwordDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionInitializer {
@@ -13621,7 +13621,7 @@ package android.service.voice {
field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
}
- @FlaggedApi("android.service.voice.flags.allow_various_attention_types") public final class VisualQueryAttentionResult implements android.os.Parcelable {
+ public final class VisualQueryAttentionResult implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=1, to=100) public int getEngagementLevel();
method public int getInteractionIntention();
@@ -13638,7 +13638,7 @@ package android.service.voice {
method @NonNull public android.service.voice.VisualQueryAttentionResult.Builder setInteractionIntention(int);
}
- @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final class VisualQueryDetectedResult implements android.os.Parcelable {
+ public final class VisualQueryDetectedResult implements android.os.Parcelable {
method public int describeContents();
method @Nullable public byte[] getAccessibilityDetectionData();
method public static int getMaxSpeakerId();
@@ -13660,16 +13660,16 @@ package android.service.voice {
ctor public VisualQueryDetectionService();
method public final void finishQuery() throws java.lang.IllegalStateException;
method public final void gainedAttention();
- method @FlaggedApi("android.service.voice.flags.allow_various_attention_types") public final void gainedAttention(@NonNull android.service.voice.VisualQueryAttentionResult);
+ method public final void gainedAttention(@NonNull android.service.voice.VisualQueryAttentionResult);
method public final void lostAttention();
- method @FlaggedApi("android.service.voice.flags.allow_various_attention_types") public final void lostAttention(int);
+ method public final void lostAttention(int);
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onStartDetection();
method public void onStopDetection();
method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
method public final void rejectQuery() throws java.lang.IllegalStateException;
method public final void streamQuery(@NonNull String) throws java.lang.IllegalStateException;
- method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final void streamQuery(@NonNull android.service.voice.VisualQueryDetectedResult);
+ method public final void streamQuery(@NonNull android.service.voice.VisualQueryDetectedResult);
field public static final String SERVICE_INTERFACE = "android.service.voice.VisualQueryDetectionService";
}
@@ -13689,10 +13689,10 @@ package android.service.voice {
}
public class VisualQueryDetector {
- method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void clearAccessibilityDetectionEnabledListener();
+ method public void clearAccessibilityDetectionEnabledListener();
method public void destroy();
- method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public boolean isAccessibilityDetectionEnabled();
- method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void setAccessibilityDetectionEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method public boolean isAccessibilityDetectionEnabled();
+ method public void setAccessibilityDetectionEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
@@ -13701,7 +13701,7 @@ package android.service.voice {
public static interface VisualQueryDetector.Callback {
method public void onFailure(@NonNull android.service.voice.VisualQueryDetectionServiceFailure);
method public void onQueryDetected(@NonNull String);
- method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public default void onQueryDetected(@NonNull android.service.voice.VisualQueryDetectedResult);
+ method public default void onQueryDetected(@NonNull android.service.voice.VisualQueryDetectedResult);
method public void onQueryFinished();
method public void onQueryRejected();
method public void onUnknownFailure(@NonNull String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a76aa6743bc5..ca70f03e5859 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2769,6 +2769,7 @@ package android.permission {
public final class PermissionManager {
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getIndicatorAppOpUsageData();
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getIndicatorAppOpUsageData(boolean);
+ method @FlaggedApi("android.permission.flags.should_register_attribution_source") public boolean isRegisteredAttributionSource(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
method @RequiresPermission(android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ff713d071a05..0ed25eb3125a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2685,8 +2685,7 @@ public class AppOpsManager {
.setDefaultMode(getSystemAlertWindowDefault()).build(),
new AppOpInfo.Builder(OP_ACCESS_NOTIFICATIONS, OPSTR_ACCESS_NOTIFICATIONS,
"ACCESS_NOTIFICATIONS")
- .setPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
- .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ .setPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS).build(),
new AppOpInfo.Builder(OP_CAMERA, OPSTR_CAMERA, "CAMERA")
.setPermission(android.Manifest.permission.CAMERA)
.setRestriction(UserManager.DISALLOW_CAMERA)
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 8b84f062b7b5..b1e7b628baa2 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -736,7 +736,8 @@ public class ResourcesManager {
private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
@NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
ResourcesImpl impl = findResourcesImplForKeyLocked(key);
- if (impl == null) {
+ // ResourcesImpl also need to be recreated if its shared library count is not up-to-date.
+ if (impl == null || impl.getSharedLibCount() != mSharedLibAssetsMap.size()) {
impl = createResourcesImpl(key, apkSupplier);
if (impl != null) {
mResourceImpls.put(key, new WeakReference<>(impl));
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 60dffbd0e421..411f7f7bb96c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -58,7 +58,6 @@ import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -13462,7 +13461,6 @@ public class DevicePolicyManager {
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional = true)
@SuppressLint("RequiresPermission")
- @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
public @Nullable SystemUpdateInfo getPendingSystemUpdate(@Nullable ComponentName admin) {
throwIfParentInstance("getPendingSystemUpdate");
try {
@@ -16638,7 +16636,6 @@ public class DevicePolicyManager {
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true)
@SuppressLint("RequiresPermission")
- @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
@NonNull public String getEnrollmentSpecificId() {
throwIfParentInstance("getEnrollmentSpecificId");
if (mService == null) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2c26389071ce..5e00b7a798d8 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1086,7 +1086,7 @@ public final class CompanionDeviceManager {
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.legacyStartObservingDevicePresence(deviceAddress,
+ mService.registerDevicePresenceListenerService(deviceAddress,
mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1128,7 +1128,7 @@ public final class CompanionDeviceManager {
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.legacyStopObservingDevicePresence(deviceAddress,
+ mService.unregisterDevicePresenceListenerService(deviceAddress,
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1328,7 +1328,7 @@ public final class CompanionDeviceManager {
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceAppeared(int associationId) {
try {
- mService.notifySelfManagedDeviceAppeared(associationId);
+ mService.notifyDeviceAppeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1350,7 +1350,7 @@ public final class CompanionDeviceManager {
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceDisappeared(int associationId) {
try {
- mService.notifySelfManagedDeviceDisappeared(associationId);
+ mService.notifyDeviceDisappeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 1b00f90e1fb3..57d59e5e5bf0 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -59,16 +59,12 @@ interface ICompanionDeviceManager {
int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void legacyStopObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+ void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
+ int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+ void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
+ int userId);
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
@@ -97,11 +93,9 @@ interface ICompanionDeviceManager {
@EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
- @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
- void notifySelfManagedDeviceAppeared(int associationId);
+ void notifyDeviceAppeared(int associationId);
- @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
- void notifySelfManagedDeviceDisappeared(int associationId);
+ void notifyDeviceDisappeared(int associationId);
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
@@ -141,4 +135,10 @@ interface ICompanionDeviceManager {
byte[] getBackupPayload(int userId);
void applyRestoredPayload(in byte[] payload, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 8d045aaf4d81..4c22fee332e6 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -146,6 +146,11 @@ public class ResourcesImpl {
// Cyclical cache used for recently-accessed XML files.
private int mLastCachedXmlBlockIndex = -1;
+
+ // The number of shared libraries registered within this ResourcesImpl, which is designed to
+ // help to determine whether this ResourcesImpl is outdated on shared library information and
+ // needs to be replaced.
+ private int mSharedLibCount;
private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
@@ -206,6 +211,7 @@ public class ResourcesImpl {
for (int i = 0; i < size; i++) {
assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
}
+ mSharedLibCount = sharedLibMap.size();
}
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
@@ -1602,4 +1608,8 @@ public class ResourcesImpl {
mSize--;
}
}
+
+ public int getSharedLibCount() {
+ return mSharedLibCount;
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 7d61c142fa04..d9614d1acd6b 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -70,6 +70,7 @@ import javax.crypto.Mac;
public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
private static final String TAG = "BiometricPrompt";
+ private static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30;
/**
* Error/help message will show for this amount of time.
@@ -222,11 +223,19 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
*
* @param logoDescription The logo description text that will be shown on the prompt.
* @return This builder.
+ * @throws IllegalStateException If logo description is null or exceeds certain character
+ * limit.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
+ if (logoDescription == null
+ || logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
+ throw new IllegalStateException(
+ "Logo description passed in can not be null or exceed "
+ + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number.");
+ }
mPromptInfo.setLogoDescription(logoDescription);
return this;
}
@@ -814,7 +823,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
/**
* Gets the logo description for the prompt, as set by
- * {@link Builder#setDescription(CharSequence)}.
+ * {@link Builder#setLogoDescription(String)}.
* Currently for system applications use only.
*
* @return The logo description of the prompt, or null if the prompt has no logo description
diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
index 9ebfa8f4301d..853d86cf94dc 100644
--- a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
+++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
@@ -40,9 +40,12 @@ import java.util.concurrent.Executor;
* or if the user has already selected the appropriate account to use before invoking
* BiometricPrompt because it will create additional steps that the user must navigate through.
* Clicking the more options button will dismiss the prompt, provide the app an opportunity to ask
- * the user for the correct account, &finally allow the app to decide how to proceed once selected.
+ * the user for the correct account, and finally allow the app to decide how to proceed once
+ * selected.
+ *
* <p>
- * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric Prompt:
+ * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric
+ * Prompt:
* <pre class="prettyprint">
* BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
* .setTitle(...)
@@ -56,6 +59,7 @@ import java.util.concurrent.Executor;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable {
+ private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
private final String mDescription;
private DialogInterface.OnClickListener mListener;
@@ -139,10 +143,15 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
*
* @param description The description to display.
* @return This builder.
+ * @throws IllegalArgumentException If description exceeds certain character limit.
*/
@NonNull
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
public Builder setDescription(@NonNull String description) {
+ if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
+ throw new IllegalStateException("The character number of description exceeds "
+ + MAX_DESCRIPTION_CHARACTER_NUMBER);
+ }
mDescription = description;
return this;
}
@@ -150,14 +159,6 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
/**
* Required: Sets the executor and click listener for the more options button on the
* prompt content.
- * This button should be used to provide more options for sign in or other purposes, such
- * as when a user needs to select between multiple app-specific accounts or profiles that
- * are available for sign in. This is not common and apps should avoid using it if there
- * is only one choice available or if the user has already selected the appropriate
- * account to use before invoking BiometricPrompt because it will create additional steps
- * that the user must navigate through. Clicking the more options button will dismiss the
- * prompt, provide the app an opportunity to ask the user for the correct account, &finally
- * allow the app to decide how to proceed once selected.
*
* @param executor Executor that will be used to run the on click callback.
* @param listener Listener containing a callback to be run when the button is pressed.
@@ -167,12 +168,6 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
public Builder setMoreOptionsButtonListener(@NonNull @CallbackExecutor Executor executor,
@NonNull DialogInterface.OnClickListener listener) {
- if (executor == null) {
- throw new IllegalArgumentException("Executor must not be null");
- }
- if (listener == null) {
- throw new IllegalArgumentException("Listener must not be null");
- }
mExecutor = executor;
mListener = listener;
return this;
@@ -183,15 +178,23 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
* Creates a {@link PromptContentViewWithMoreOptionsButton}.
*
* @return An instance of {@link PromptContentViewWithMoreOptionsButton}.
+ * @throws IllegalArgumentException If the executor of more options button is null, or the
+ * listener of more options button is null.
*/
@NonNull
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
public PromptContentViewWithMoreOptionsButton build() {
- if (mListener == null) {
+ if (mExecutor == null) {
throw new IllegalArgumentException(
- "The listener of more options button on prompt content must be set if "
+ "The executor for the listener of more options button on prompt content "
+ + "must be set and non-null if "
+ "PromptContentViewWithMoreOptionsButton is used.");
}
+ if (mListener == null) {
+ throw new IllegalArgumentException(
+ "The listener of more options button on prompt content must be set and "
+ + "non-null if PromptContentViewWithMoreOptionsButton is used.");
+ }
return new PromptContentViewWithMoreOptionsButton(mDescription, mExecutor, mListener);
}
}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 814321319e2f..18b75c9b8f8f 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -173,14 +173,19 @@ public class PromptInfo implements Parcelable {
/**
* Returns whether SET_BIOMETRIC_DIALOG_ADVANCED is contained.
+ *
+ * Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs
+ * this permission.
*/
- public boolean containsSetLogoApiConfigurations() {
+ public boolean containsAdvancedApiConfigurations() {
if (mLogoRes != -1) {
return true;
} else if (mLogoBitmap != null) {
return true;
} else if (mLogoDescription != null) {
return true;
+ } else if (mContentView != null && isContentViewMoreOptionsButtonUsed()) {
+ return true;
}
return false;
}
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index 38d32dc73ccb..02b2a50ade3c 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -29,9 +29,7 @@ import java.util.List;
/**
- * Contains the information of the template of vertical list content view for Biometric Prompt. Note
- * that there are limits on the item count and the number of characters allowed for each item's
- * text.
+ * Contains the information of the template of vertical list content view for Biometric Prompt.
* <p>
* Here's how you'd set a <code>PromptVerticalListContentView</code> on a Biometric Prompt:
* <pre class="prettyprint">
@@ -51,6 +49,8 @@ import java.util.List;
public final class PromptVerticalListContentView implements PromptContentViewParcelable {
private static final int MAX_ITEM_NUMBER = 20;
private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
+ private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
+
private final List<PromptContentItemParcelable> mContentList;
private final String mDescription;
@@ -150,51 +150,59 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
*
* @param description The description to display.
* @return This builder.
+ * @throws IllegalArgumentException If description exceeds certain character limit.
*/
@NonNull
public Builder setDescription(@NonNull String description) {
+ if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
+ throw new IllegalStateException("The character number of description exceeds "
+ + MAX_DESCRIPTION_CHARACTER_NUMBER);
+ }
mDescription = description;
return this;
}
/**
- * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
- * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
- * characters.
+ * Optional: Adds a list item in the current row.
*
* @param listItem The list item view to display
* @return This builder.
+ * @throws IllegalArgumentException If this list item exceeds certain character limits or
+ * the number of list items exceeds certain limit.
*/
@NonNull
public Builder addListItem(@NonNull PromptContentItem listItem) {
- if (doesListItemExceedsCharLimit(listItem)) {
- throw new IllegalStateException(
- "The character number of list item exceeds "
- + MAX_EACH_ITEM_CHARACTER_NUMBER);
- }
mContentList.add((PromptContentItemParcelable) listItem);
+ checkItemLimits(listItem);
return this;
}
-
/**
- * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
- * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
- * characters.
+ * Optional: Adds a list item in the current row.
*
* @param listItem The list item view to display
- * @param index The position at which to add the item
+ * @param index The position at which to add the item
* @return This builder.
+ * @throws IllegalArgumentException If this list item exceeds certain character limits or
+ * the number of list items exceeds certain limit.
*/
@NonNull
public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
+ mContentList.add(index, (PromptContentItemParcelable) listItem);
+ checkItemLimits(listItem);
+ return this;
+ }
+
+ private void checkItemLimits(@NonNull PromptContentItem listItem) {
if (doesListItemExceedsCharLimit(listItem)) {
throw new IllegalStateException(
"The character number of list item exceeds "
+ MAX_EACH_ITEM_CHARACTER_NUMBER);
}
- mContentList.add(index, (PromptContentItemParcelable) listItem);
- return this;
+ if (mContentList.size() > MAX_ITEM_NUMBER) {
+ throw new IllegalStateException(
+ "The number of list items exceeds " + MAX_ITEM_NUMBER);
+ }
}
private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
@@ -217,10 +225,6 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
*/
@NonNull
public PromptVerticalListContentView build() {
- if (mContentList.size() > MAX_ITEM_NUMBER) {
- throw new IllegalStateException(
- "The number of list items exceeds " + MAX_ITEM_NUMBER);
- }
return new PromptVerticalListContentView(mContentList, mDescription);
}
}
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index f927b8b52912..0d9db1fa3c91 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -37,6 +37,7 @@ import android.provider.Settings.Secure;
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.display.feature.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -533,11 +534,15 @@ public final class ColorDisplayManager {
/**
* Returns {@code true} if reduce bright colors is supported by the device.
+ * Will return false if even dimmer is enabled - since this is the successor to RBC and cannot
+ * be run concurrently.
*
* @hide
*/
public static boolean isReduceBrightColorsAvailable(Context context) {
- return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable);
+ return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable)
+ && !(Flags.evenDimmer() && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled));
}
/**
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 3c7692d03410..3441244d6c58 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.os.Build.VERSION_CODES.S;
+import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;
import static android.permission.flags.Flags.serverSideAttributionRegistration;
import android.Manifest;
@@ -1652,6 +1653,8 @@ public final class PermissionManager {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) {
try {
return mPermissionManager.isRegisteredAttributionSource(source.asState());
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec5421e789f8..c3b646ae34e9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11185,6 +11185,32 @@ public final class Settings {
public static final String DISPLAY_WHITE_BALANCE_ENABLED = "display_white_balance_enabled";
/**
+ * Used by DisplayManager to backup/restore the user-selected resolution mode.
+ * @hide
+ */
+ @Readable
+ public static final String SCREEN_RESOLUTION_MODE = "screen_resolution_mode";
+
+ /**
+ * Resolution Mode Constants for SCREEN_RESOLUTION_MODE setting.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "RESOLUTION_MODE_" }, value = {
+ RESOLUTION_MODE_UNKNOWN,
+ RESOLUTION_MODE_HIGH,
+ RESOLUTION_MODE_FULL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResolutionMode {}
+ /** @hide */
+ public static final int RESOLUTION_MODE_UNKNOWN = 0;
+ /** @hide */
+ public static final int RESOLUTION_MODE_HIGH = 1;
+ /** @hide */
+ public static final int RESOLUTION_MODE_FULL = 2;
+
+ /**
* Names of the service components that the current user has explicitly allowed to
* be a VR mode listener, separated by ':'.
*
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index d8210742e331..264b53c6ee40 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -174,23 +174,6 @@ public class StatusBarNotification implements Parcelable {
return sbnKey;
}
- /**
- * @return Whether the Entry is a group child by the app or system
- * @hide
- */
- public boolean isAppOrSystemGroupChild() {
- return isGroup() && !getNotification().isGroupSummary();
- }
-
-
- /**
- * @return Whether the Entry is a group summary by the app or system
- * @hide
- */
- public boolean isAppOrSystemGroupSummary() {
- return isGroup() && getNotification().isGroupSummary();
- }
-
private String groupKey() {
if (overrideGroupKey != null) {
return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index df4a2bb6afb6..0b7b19962773 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -16,10 +16,10 @@
package android.service.voice;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.res.Resources;
import android.media.AudioRecord;
@@ -27,7 +27,6 @@ import android.media.MediaSyncEvent;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.service.voice.flags.Flags;
import com.android.internal.R;
import com.android.internal.util.DataClass;
@@ -142,7 +141,7 @@ public final class HotwordDetectedResult implements Parcelable {
}
/** Maximum number of active speaker ids. **/
- @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public static int getMaxSpeakerId() {
return 15;
}
@@ -666,7 +665,7 @@ public final class HotwordDetectedResult implements Parcelable {
* <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
*/
@DataClass.Generated.Member
- @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public int getSpeakerId() {
return mSpeakerId;
}
@@ -988,7 +987,7 @@ public final class HotwordDetectedResult implements Parcelable {
* <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
*/
@DataClass.Generated.Member
- @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @NonNull Builder setSpeakerId(int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
@@ -1232,10 +1231,10 @@ public final class HotwordDetectedResult implements Parcelable {
}
@DataClass.Generated(
- time = 1709773165191L,
+ time = 1710918729668L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
- inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate final int mBackgroundAudioPower\nprivate static int defaultSpeakerId()\npublic static @android.annotation.FlaggedApi int getMaxSpeakerId()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate final int mBackgroundAudioPower\nprivate static int defaultSpeakerId()\npublic static @android.annotation.SuppressLint int getMaxSpeakerId()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 763c79e20846..5f9f6bde3129 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -3,5 +3,4 @@
include /core/java/android/app/assist/OWNERS
# The owner here should not be assist owner
-liangyuchen@google.com
adudani@google.com
diff --git a/core/java/android/service/voice/VisualQueryAttentionResult.java b/core/java/android/service/voice/VisualQueryAttentionResult.java
index 690990b46ed6..aad49d530671 100644
--- a/core/java/android/service/voice/VisualQueryAttentionResult.java
+++ b/core/java/android/service/voice/VisualQueryAttentionResult.java
@@ -16,15 +16,14 @@
package android.service.voice;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.voice.flags.Flags;
import com.android.internal.util.DataClass;
@@ -45,13 +44,15 @@ import java.lang.annotation.RetentionPolicy;
genToString = true
)
@SystemApi
-@FlaggedApi(Flags.FLAG_ALLOW_VARIOUS_ATTENTION_TYPES)
+@SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public final class VisualQueryAttentionResult implements Parcelable {
/** Intention type to allow the system to listen to audio-visual query interactions. */
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public static final int INTERACTION_INTENTION_AUDIO_VISUAL = 0;
/** Intention type to allow the system to listen to visual accessibility query interactions. */
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public static final int INTERACTION_INTENTION_VISUAL_ACCESSIBILITY = 1;
/**
@@ -94,6 +95,16 @@ public final class VisualQueryAttentionResult implements Parcelable {
.setEngagementLevel(mEngagementLevel);
}
+ /**
+ * TODO(b/301491148): Remove suppressLint on generated API when fixed or sdk finalized.
+ * Codegen does not support flaggedAPI, so needs to review manually on the generated code
+ * and makes sure the following:
+ * 1. SuppressLint is added back to the API after each run of codegen
+ * 2. No unwanted method is modified due to suppressLint annotation
+ *
+ * Run $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisualQueryAttentionResult.java
+ * for codegen on new APIs.
+ */
@@ -112,8 +123,8 @@ public final class VisualQueryAttentionResult implements Parcelable {
/** @hide */
@IntDef(prefix = "INTERACTION_INTENTION_", value = {
- INTERACTION_INTENTION_AUDIO_VISUAL,
- INTERACTION_INTENTION_VISUAL_ACCESSIBILITY
+ INTERACTION_INTENTION_AUDIO_VISUAL,
+ INTERACTION_INTENTION_VISUAL_ACCESSIBILITY
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -124,15 +135,15 @@ public final class VisualQueryAttentionResult implements Parcelable {
public static String interactionIntentionToString(@InteractionIntention int value) {
switch (value) {
case INTERACTION_INTENTION_AUDIO_VISUAL:
- return "INTERACTION_INTENTION_AUDIO_VISUAL";
+ return "INTERACTION_INTENTION_AUDIO_VISUAL";
case INTERACTION_INTENTION_VISUAL_ACCESSIBILITY:
- return "INTERACTION_INTENTION_VISUAL_ACCESSIBILITY";
+ return "INTERACTION_INTENTION_VISUAL_ACCESSIBILITY";
default: return Integer.toHexString(value);
}
}
@DataClass.Generated.Member
- /* package-private */ VisualQueryAttentionResult(
+ /* package-private */ VisualQueryAttentionResult(
@InteractionIntention int interactionIntention,
@IntRange(from = 1, to = 100) int engagementLevel) {
this.mInteractionIntention = interactionIntention;
@@ -159,6 +170,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
* to after the attention signal is gained.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public @InteractionIntention int getInteractionIntention() {
return mInteractionIntention;
}
@@ -176,6 +188,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
* presentation of the device attention UI.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public @IntRange(from = 1, to = 100) int getEngagementLevel() {
return mEngagementLevel;
}
@@ -187,7 +200,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
// String fieldNameToString() { ... }
return "VisualQueryAttentionResult { " +
- "interactionIntention = " + interactionIntentionToString(mInteractionIntention) + ", " +
+ "interactionIntention = " + mInteractionIntention + ", " +
"engagementLevel = " + mEngagementLevel +
" }";
}
@@ -223,6 +236,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
@Override
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -233,6 +247,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
@Override
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public int describeContents() { return 0; }
/** @hide */
@@ -246,7 +261,6 @@ public final class VisualQueryAttentionResult implements Parcelable {
int engagementLevel = in.readInt();
this.mInteractionIntention = interactionIntention;
-
if (!(mInteractionIntention == INTERACTION_INTENTION_AUDIO_VISUAL)
&& !(mInteractionIntention == INTERACTION_INTENTION_VISUAL_ACCESSIBILITY)) {
throw new java.lang.IllegalArgumentException(
@@ -254,7 +268,6 @@ public final class VisualQueryAttentionResult implements Parcelable {
+ "INTERACTION_INTENTION_AUDIO_VISUAL(" + INTERACTION_INTENTION_AUDIO_VISUAL + "), "
+ "INTERACTION_INTENTION_VISUAL_ACCESSIBILITY(" + INTERACTION_INTENTION_VISUAL_ACCESSIBILITY + ")");
}
-
this.mEngagementLevel = engagementLevel;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mEngagementLevel,
@@ -265,6 +278,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
}
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public static final @NonNull Parcelable.Creator<VisualQueryAttentionResult> CREATOR
= new Parcelable.Creator<VisualQueryAttentionResult>() {
@Override
@@ -283,6 +297,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public static final class Builder {
private @InteractionIntention int mInteractionIntention;
@@ -290,6 +305,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
private long mBuilderFieldsSet = 0L;
+ @SuppressLint("UnflaggedApi")
public Builder() {
}
@@ -298,6 +314,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
* to after the attention signal is gained.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public @NonNull Builder setInteractionIntention(@InteractionIntention int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
@@ -318,6 +335,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
* presentation of the device attention UI.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi")
public @NonNull Builder setEngagementLevel(@IntRange(from = 1, to = 100) int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
@@ -326,6 +344,7 @@ public final class VisualQueryAttentionResult implements Parcelable {
}
/** Builds the instance. This builder should not be touched after calling this! */
+ @SuppressLint("UnflaggedApi")
public @NonNull VisualQueryAttentionResult build() {
checkNotUsed();
mBuilderFieldsSet |= 0x4; // Mark builder used
@@ -351,10 +370,10 @@ public final class VisualQueryAttentionResult implements Parcelable {
}
@DataClass.Generated(
- time = 1707773691880L,
+ time = 1710979945907L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryAttentionResult.java",
- inputSignatures = "public static final int INTERACTION_INTENTION_AUDIO_VISUAL\npublic static final int INTERACTION_INTENTION_VISUAL_ACCESSIBILITY\nprivate final @android.service.voice.VisualQueryAttentionResult.InteractionIntention int mInteractionIntention\nprivate final @android.annotation.IntRange int mEngagementLevel\nprivate static @android.service.voice.VisualQueryAttentionResult.InteractionIntention int defaultInteractionIntention()\nprivate static int defaultEngagementLevel()\npublic android.service.voice.VisualQueryAttentionResult.Builder buildUpon()\nclass VisualQueryAttentionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final @android.annotation.SuppressLint int INTERACTION_INTENTION_AUDIO_VISUAL\npublic static final @android.annotation.SuppressLint int INTERACTION_INTENTION_VISUAL_ACCESSIBILITY\nprivate final @android.service.voice.VisualQueryAttentionResult.InteractionIntention int mInteractionIntention\nprivate final @android.annotation.IntRange int mEngagementLevel\nprivate static @android.service.voice.VisualQueryAttentionResult.InteractionIntention int defaultInteractionIntention()\nprivate static int defaultEngagementLevel()\npublic android.service.voice.VisualQueryAttentionResult.Builder buildUpon()\nclass VisualQueryAttentionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/VisualQueryDetectedResult.java b/core/java/android/service/voice/VisualQueryDetectedResult.java
index 3b617948b41e..74b7da3cbe8a 100644
--- a/core/java/android/service/voice/VisualQueryDetectedResult.java
+++ b/core/java/android/service/voice/VisualQueryDetectedResult.java
@@ -16,13 +16,12 @@
package android.service.voice;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.voice.flags.Flags;
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
@@ -43,7 +42,7 @@ import java.util.Objects;
genToString = true
)
@SystemApi
-@FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
+@SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public final class VisualQueryDetectedResult implements Parcelable {
/**
@@ -64,6 +63,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
return 0;
}
/** Maximum number of active speaker ids. **/
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public static int getMaxSpeakerId() {
return 15;
}
@@ -100,6 +100,16 @@ public final class VisualQueryDetectedResult implements Parcelable {
.setAccessibilityDetectionData(mAccessibilityDetectionData);
}
+ /**
+ * TODO(b/301491148): Remove suppressLint on generated API when fixed or sdk finalized.
+ * Codegen does not support flaggedAPI, so needs to review manually on the generated code
+ * and makes sure the following:
+ * 1. SuppressLint is added back to the API after each run of codegen
+ * 2. No unwanted method is modified due to suppressLint annotation
+ *
+ * Run $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisualQueryAttentionResult.java
+ * for codegen on new APIs.
+ */
// Code below generated by codegen v1.0.23.
//
@@ -132,6 +142,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
* Text query being associated with the detection result.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @NonNull String getPartialQuery() {
return mPartialQuery;
}
@@ -142,6 +153,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
* <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public int getSpeakerId() {
return mSpeakerId;
}
@@ -152,6 +164,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
* {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)}
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @Nullable byte[] getAccessibilityDetectionData() {
return mAccessibilityDetectionData;
}
@@ -202,6 +215,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
@Override
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -213,6 +227,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
@Override
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public int describeContents() { return 0; }
/** @hide */
@@ -236,6 +251,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
}
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public static final @NonNull Parcelable.Creator<VisualQueryDetectedResult> CREATOR
= new Parcelable.Creator<VisualQueryDetectedResult>() {
@Override
@@ -254,6 +270,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public static final class Builder {
private @NonNull String mPartialQuery;
@@ -262,6 +279,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
private long mBuilderFieldsSet = 0L;
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public Builder() {
}
@@ -269,6 +287,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
* Text query being associated with the detection result.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @NonNull Builder setPartialQuery(@NonNull String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
@@ -282,6 +301,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
* <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @NonNull Builder setSpeakerId(int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
@@ -295,6 +315,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
* {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)}
*/
@DataClass.Generated.Member
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @NonNull Builder setAccessibilityDetectionData(@NonNull byte... value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
@@ -303,6 +324,7 @@ public final class VisualQueryDetectedResult implements Parcelable {
}
/** Builds the instance. This builder should not be touched after calling this! */
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public @NonNull VisualQueryDetectedResult build() {
checkNotUsed();
mBuilderFieldsSet |= 0x8; // Mark builder used
@@ -332,10 +354,10 @@ public final class VisualQueryDetectedResult implements Parcelable {
}
@DataClass.Generated(
- time = 1707429290528L,
+ time = 1710958903381L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryDetectedResult.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final int mSpeakerId\nprivate final @android.annotation.Nullable byte[] mAccessibilityDetectionData\nprivate static java.lang.String defaultPartialQuery()\nprivate static int defaultSpeakerId()\npublic static int getMaxSpeakerId()\nprivate static byte[] defaultAccessibilityDetectionData()\nprivate void onConstructed()\npublic android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final int mSpeakerId\nprivate final @android.annotation.Nullable byte[] mAccessibilityDetectionData\nprivate static java.lang.String defaultPartialQuery()\nprivate static int defaultSpeakerId()\npublic static @android.annotation.SuppressLint int getMaxSpeakerId()\nprivate static byte[] defaultAccessibilityDetectionData()\nprivate void onConstructed()\npublic android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index b9f4c3272207..8c9731d12df3 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -17,7 +17,6 @@
package android.service.voice;
import android.annotation.DurationMillisLong;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -36,7 +35,6 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
-import android.service.voice.flags.Flags;
import android.speech.IRecognitionServiceManager;
import android.util.Log;
import android.view.contentcapture.ContentCaptureManager;
@@ -272,14 +270,10 @@ public abstract class VisualQueryDetectionService extends Service
*
*/
public final void gainedAttention() {
- if (Flags.allowVariousAttentionTypes()) {
- gainedAttention(new VisualQueryAttentionResult.Builder().build());
- } else {
- try {
- mRemoteCallback.onAttentionGained(null);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ mRemoteCallback.onAttentionGained(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -299,7 +293,7 @@ public abstract class VisualQueryDetectionService extends Service
*
* @param attentionResult Attention result of type {@link VisualQueryAttentionResult}.
*/
- @FlaggedApi(Flags.FLAG_ALLOW_VARIOUS_ATTENTION_TYPES)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public final void gainedAttention(@NonNull VisualQueryAttentionResult attentionResult) {
try {
mRemoteCallback.onAttentionGained(attentionResult);
@@ -312,15 +306,10 @@ public abstract class VisualQueryDetectionService extends Service
* Informs the system that all attention has lost to stop streaming.
*/
public final void lostAttention() {
- if (Flags.allowVariousAttentionTypes()) {
- lostAttention(VisualQueryAttentionResult.INTERACTION_INTENTION_AUDIO_VISUAL);
- lostAttention(VisualQueryAttentionResult.INTERACTION_INTENTION_VISUAL_ACCESSIBILITY);
- } else {
- try {
- mRemoteCallback.onAttentionLost(0); // placeholder
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ mRemoteCallback.onAttentionLost(0); // placeholder
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -332,7 +321,7 @@ public abstract class VisualQueryDetectionService extends Service
* @param interactionIntention Interaction intention, one of
* {@link VisualQueryAttentionResult#InteractionIntention}.
*/
- @FlaggedApi(Flags.FLAG_ALLOW_VARIOUS_ATTENTION_TYPES)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public final void lostAttention(
@VisualQueryAttentionResult.InteractionIntention int interactionIntention) {
try {
@@ -375,7 +364,7 @@ public abstract class VisualQueryDetectionService extends Service
* @param partialResult Partially detected result in the format of
* {@link VisualQueryDetectedResult}.
*/
- @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public final void streamQuery(@NonNull VisualQueryDetectedResult partialResult) {
Objects.requireNonNull(partialResult);
try {
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 11858e841a8f..cd1d125708ac 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -20,7 +20,6 @@ import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.RECORD_AUDIO;
import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -34,7 +33,6 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
-import android.service.voice.flags.Flags;
import android.text.TextUtils;
import android.util.Slog;
@@ -187,7 +185,7 @@ public class VisualQueryDetector {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public boolean isAccessibilityDetectionEnabled() {
Slog.d(TAG, "Fetching accessibility setting");
synchronized (mInitializationDelegate.getLock()) {
@@ -214,7 +212,7 @@ public class VisualQueryDetector {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public void setAccessibilityDetectionEnabledListener(@NonNull Consumer<Boolean> listener) {
Slog.d(TAG, "Registering Accessibility settings listener.");
synchronized (mInitializationDelegate.getLock()) {
@@ -247,7 +245,7 @@ public class VisualQueryDetector {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
public void clearAccessibilityDetectionEnabledListener() {
Slog.d(TAG, "Unregistering Accessibility settings listener.");
synchronized (mInitializationDelegate.getLock()) {
@@ -327,7 +325,7 @@ public class VisualQueryDetector {
*
* @param partialResult The partial query in a text form being streamed.
*/
- @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS)
+ @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process
default void onQueryDetected(@NonNull VisualQueryDetectedResult partialResult) {
throw new UnsupportedOperationException("This emthod must be implemented for use.");
}
diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig
index 633304b94a5f..1ae7d8c34442 100644
--- a/core/java/android/service/voice/flags/flags.aconfig
+++ b/core/java/android/service/voice/flags/flags.aconfig
@@ -22,28 +22,4 @@ flag {
namespace: "machine_learning"
description: "This flag allows providing foreground app component along with onShow args."
bug: "319409708"
-}
-
-flag {
- name: "allow_various_attention_types"
- is_exported: true
- namespace: "visual_query"
- description: "This flag allows visual query detection service to set different attention types."
- bug: "318617199"
-}
-
-flag {
- name: "allow_complex_results_egress_from_vqds"
- is_exported: true
- namespace: "visual_query"
- description: "This flag allows visual query detection service egress detailed results. "
- bug: "318617199"
-}
-
-flag {
- name: "allow_speaker_id_egress"
- is_exported: true
- namespace: "machine_learning"
- description: "This flag allows hotword detection service and visual query detection service to egress current speaker profile id."
- bug: "318617199"
} \ No newline at end of file
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 2b6684ea8f1d..38aeb3757eae 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -700,6 +700,23 @@ public class MeasuredParagraph {
bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
}
mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+
+ if (mCopiedBuffer.length > 0
+ && mBidi.getParagraphIndex(mCopiedBuffer.length - 1) != 0) {
+ // Historically, the MeasuredParagraph does not treat the CR letters as paragraph
+ // breaker but ICU BiDi treats it as paragraph breaker. In the MeasureParagraph,
+ // the given range always represents a single paragraph, so if the BiDi object has
+ // multiple paragraph, it should contains a CR letters in the text. Using CR is not
+ // common in Android and also it should not penalize the easy case, e.g. all LTR,
+ // check the paragraph count here and replace the CR letters and re-calculate
+ // BiDi again.
+ for (int i = 0; i < mTextLength; ++i) {
+ if (mCopiedBuffer[i] == '\r') {
+ mCopiedBuffer[i] = OBJECT_REPLACEMENT_CHARACTER;
+ }
+ }
+ mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+ }
mLevels.resize(mTextLength);
byte[] rawArray = mLevels.getRawArray();
for (int i = 0; i < mTextLength; ++i) {
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 9099f9855eab..147d56253dcf 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -36,6 +36,7 @@ import android.graphics.drawable.VectorDrawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PointerIconType;
import android.util.Log;
import android.util.SparseArray;
@@ -52,10 +53,10 @@ public final class PointerIcon implements Parcelable {
private static final String TAG = "PointerIcon";
/** {@hide} Type constant: Custom icon with a user-supplied bitmap. */
- public static final int TYPE_CUSTOM = -1;
+ public static final int TYPE_CUSTOM = PointerIconType.CUSTOM;
/** Type constant: Null icon. It has no bitmap. */
- public static final int TYPE_NULL = 0;
+ public static final int TYPE_NULL = PointerIconType.TYPE_NULL;
/**
* Type constant: no icons are specified. If all views uses this, then the pointer icon falls
@@ -63,63 +64,63 @@ public final class PointerIcon implements Parcelable {
* to have the default icon.
* @hide
*/
- public static final int TYPE_NOT_SPECIFIED = 1;
+ public static final int TYPE_NOT_SPECIFIED = PointerIconType.NOT_SPECIFIED;
/** Type constant: Arrow icon. (Default mouse pointer) */
- public static final int TYPE_ARROW = 1000;
+ public static final int TYPE_ARROW = PointerIconType.ARROW;
/** {@hide} Type constant: Spot hover icon for touchpads. */
- public static final int TYPE_SPOT_HOVER = 2000;
+ public static final int TYPE_SPOT_HOVER = PointerIconType.SPOT_HOVER;
/** {@hide} Type constant: Spot touch icon for touchpads. */
- public static final int TYPE_SPOT_TOUCH = 2001;
+ public static final int TYPE_SPOT_TOUCH = PointerIconType.SPOT_TOUCH;
/** {@hide} Type constant: Spot anchor icon for touchpads. */
- public static final int TYPE_SPOT_ANCHOR = 2002;
+ public static final int TYPE_SPOT_ANCHOR = PointerIconType.SPOT_ANCHOR;
// Type constants for additional predefined icons for mice.
/** Type constant: context-menu. */
- public static final int TYPE_CONTEXT_MENU = 1001;
+ public static final int TYPE_CONTEXT_MENU = PointerIconType.CONTEXT_MENU;
/** Type constant: hand. */
- public static final int TYPE_HAND = 1002;
+ public static final int TYPE_HAND = PointerIconType.HAND;
/** Type constant: help. */
- public static final int TYPE_HELP = 1003;
+ public static final int TYPE_HELP = PointerIconType.HELP;
/** Type constant: wait. */
- public static final int TYPE_WAIT = 1004;
+ public static final int TYPE_WAIT = PointerIconType.WAIT;
/** Type constant: cell. */
- public static final int TYPE_CELL = 1006;
+ public static final int TYPE_CELL = PointerIconType.CELL;
/** Type constant: crosshair. */
- public static final int TYPE_CROSSHAIR = 1007;
+ public static final int TYPE_CROSSHAIR = PointerIconType.CROSSHAIR;
/** Type constant: text. */
- public static final int TYPE_TEXT = 1008;
+ public static final int TYPE_TEXT = PointerIconType.TEXT;
/** Type constant: vertical-text. */
- public static final int TYPE_VERTICAL_TEXT = 1009;
+ public static final int TYPE_VERTICAL_TEXT = PointerIconType.VERTICAL_TEXT;
/** Type constant: alias (indicating an alias of/shortcut to something is
* to be created. */
- public static final int TYPE_ALIAS = 1010;
+ public static final int TYPE_ALIAS = PointerIconType.ALIAS;
/** Type constant: copy. */
- public static final int TYPE_COPY = 1011;
+ public static final int TYPE_COPY = PointerIconType.COPY;
/** Type constant: no-drop. */
- public static final int TYPE_NO_DROP = 1012;
+ public static final int TYPE_NO_DROP = PointerIconType.NO_DROP;
/** Type constant: all-scroll. */
- public static final int TYPE_ALL_SCROLL = 1013;
+ public static final int TYPE_ALL_SCROLL = PointerIconType.ALL_SCROLL;
/** Type constant: horizontal double arrow mainly for resizing. */
- public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014;
+ public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = PointerIconType.HORIZONTAL_DOUBLE_ARROW;
/** Type constant: vertical double arrow mainly for resizing. */
- public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015;
+ public static final int TYPE_VERTICAL_DOUBLE_ARROW = PointerIconType.VERTICAL_DOUBLE_ARROW;
/** Type constant: diagonal double arrow -- top-right to bottom-left. */
public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
@@ -128,19 +129,19 @@ public final class PointerIcon implements Parcelable {
public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
/** Type constant: zoom-in. */
- public static final int TYPE_ZOOM_IN = 1018;
+ public static final int TYPE_ZOOM_IN = PointerIconType.ZOOM_IN;
/** Type constant: zoom-out. */
- public static final int TYPE_ZOOM_OUT = 1019;
+ public static final int TYPE_ZOOM_OUT = PointerIconType.ZOOM_OUT;
/** Type constant: grab. */
- public static final int TYPE_GRAB = 1020;
+ public static final int TYPE_GRAB = PointerIconType.GRAB;
/** Type constant: grabbing. */
- public static final int TYPE_GRABBING = 1021;
+ public static final int TYPE_GRABBING = PointerIconType.GRABBING;
/** Type constant: handwriting. */
- public static final int TYPE_HANDWRITING = 1022;
+ public static final int TYPE_HANDWRITING = PointerIconType.HANDWRITING;
// OEM private types should be defined starting at this range to avoid
// conflicts with any system types that may be defined in the future.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c61854c89f0..85d3688a9a1e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -104,7 +104,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
+import static android.view.accessibility.Flags.fixMergedContentChangeEventV2;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
@@ -11796,7 +11796,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mSource != null) {
- if (fixMergedContentChangeEvent()) {
+ if (fixMergedContentChangeEventV2()) {
View newSource = getCommonPredecessor(mSource, source);
if (newSource != null) {
newSource = newSource.getSelfOrParentImportantForA11y();
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 91bd4ea0bc87..eefc72b82c24 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -56,9 +56,12 @@ flag {
flag {
namespace: "accessibility"
- name: "fix_merged_content_change_event"
+ name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 40e28cbbbd05..163e43a009e7 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -155,6 +155,14 @@ public class BackProgressAnimator {
mSpring.animateToFinalPosition(0);
}
+ /**
+ * Removes the finishCallback passed into {@link #onBackCancelled}
+ */
+ public void removeOnBackCancelledFinishCallback() {
+ mSpring.removeEndListener(mOnAnimationEndListener);
+ mBackCancelledFinishRunnable = null;
+ }
+
/** Returns true if the back animation is in progress. */
boolean isBackAnimationInProgress() {
return mBackAnimationInProgress;
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 2a4f062478bd..593bdf0812aa 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -1,6 +1,5 @@
# Camera
-per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@google.com
-per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com
+per-file *Camera*,*camera* = file:platform/frameworks/av:/camera/OWNERS
# Connectivity
per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index fcc85b7ec90f..5ae365cc92b4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -249,6 +249,11 @@ message SecureSettingsProto {
}
optional DateTime date_time = 90;
+ message Display {
+ optional SettingProto screen_resolution_mode = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional Display display = 100;
+
message Doze {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -727,5 +732,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 100;
+ // Next tag = 101;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 31f2d8b13794..6431db9d174e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6778,7 +6778,7 @@
android:protectionLevel="signature" />
<!-- Allows an application to set the advanced features on BiometricDialog (SystemUI), including
- logo, logo description.
+ logo, logo description, and content view with more options button.
<p>Not for use by third-party applications.
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
-->
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index be0669c42d44..f87a9e2b3643 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -72,6 +72,7 @@ import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.MergedConfiguration;
import android.view.Display;
import android.view.View;
@@ -109,6 +110,9 @@ import java.util.function.Consumer;
@MediumTest
@Presubmit
public class ActivityThreadTest {
+
+ private static final String TAG = "ActivityThreadTest";
+
private static final int TIMEOUT_SEC = 10;
// The first sequence number to try with. Use a large number to avoid conflicts with the first a
@@ -968,8 +972,15 @@ public class ActivityThreadTest {
@NonNull
private static ClientTransaction newRelaunchResumeTransaction(@NonNull Activity activity) {
final Configuration currentConfig = activity.getResources().getConfiguration();
- final ActivityWindowInfo activityWindowInfo = getActivityClientRecord(activity)
- .getActivityWindowInfo();
+ final ActivityClientRecord record = getActivityClientRecord(activity);
+ final ActivityWindowInfo activityWindowInfo;
+ if (record == null) {
+ Log.d(TAG, "The ActivityClientRecord of r=" + activity + " is not created yet. "
+ + "Likely because this call doesn't wait until activity launch.");
+ activityWindowInfo = new ActivityWindowInfo();
+ } else {
+ activityWindowInfo = record.getActivityWindowInfo();
+ }
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
activity.getActivityToken(), null, null, 0,
new MergedConfiguration(currentConfig, currentConfig),
diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
index c1b6666a2d17..fd1add9b4468 100644
--- a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
+++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java
@@ -16,7 +16,7 @@
package android.app.activity;
-import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
import static com.google.common.truth.Truth.assertThat;
@@ -67,7 +67,7 @@ public class RegisterComponentCallbacksTest {
config.windowConfiguration.setWindowingMode(
WindowConfiguration.WINDOWING_MODE_FREEFORM);
config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
- final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;
+ final int trimMemoryLevel = TRIM_MEMORY_BACKGROUND;
scenario.onActivity(activity -> {
// It should be no-op to unregister a ComponentCallbacks without registration.
@@ -98,7 +98,7 @@ public class RegisterComponentCallbacksTest {
config.windowConfiguration.setWindowingMode(
WindowConfiguration.WINDOWING_MODE_FREEFORM);
config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
- final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;
+ final int trimMemoryLevel = TRIM_MEMORY_BACKGROUND;
scenario.onActivity(activity -> {
// It should be no-op to unregister a ComponentCallbacks without registration.
diff --git a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
index 921866bfe5e9..22e88065ac2c 100644
--- a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
+++ b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java
@@ -28,6 +28,8 @@ import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,6 +54,10 @@ public class WindowMetricsHelperTest {
@Test
public void testGetLegacySizeMatchesDisplayGetSize() throws Throwable {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Introduce new test to cover the new behavior.
+ return;
+ }
mActivityRule.runOnUiThread(() -> {
Activity activity = mActivityRule.getActivity();
final WindowMetrics metrics = activity.getWindowManager().getCurrentWindowMetrics();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ea3235bfff6c..fc4277e60bc5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -42,6 +42,10 @@ applications that come with the platform
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.credentialmanager">
+ <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
+ </privapp-permissions>
+
<privapp-permissions package="com.android.externalstorage">
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 9bd8531d33dc..9b9798c6d93b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -147,7 +147,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final Runnable mAnimationTimeoutRunnable = () -> {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
MAX_ANIMATION_DURATION);
- onBackAnimationFinished();
+ finishBackAnimation();
};
private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@@ -156,6 +156,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Nullable
private IOnBackInvokedCallback mActiveCallback;
+ @Nullable
+ private RemoteAnimationTarget[] mApps;
@VisibleForTesting
final RemoteCallback mNavigationObserver = new RemoteCallback(
@@ -466,6 +468,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
+ boolean interruptCancelPostCommitAnimation = mPostCommitAnimationInProgress
+ && mCurrentTracker.isFinished() && !mCurrentTracker.getTriggerBack()
+ && mQueuedTracker.isInInitialState();
+ if (interruptCancelPostCommitAnimation) {
+ // If a system animation is currently in the post-commit phase animating an
+ // onBackCancelled event, let's interrupt it and start animating a new back gesture
+ resetTouchTracker();
+ }
TouchTracker touchTracker;
if (mCurrentTracker.isInInitialState()) {
touchTracker = mCurrentTracker;
@@ -480,9 +490,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
touchTracker.setState(TouchTracker.TouchTrackerState.ACTIVE);
mBackGestureStarted = true;
- if (touchTracker == mCurrentTracker) {
+ if (interruptCancelPostCommitAnimation) {
+ // post-commit cancel is currently running. let's interrupt it and dispatch a new
+ // onBackStarted event.
+ mPostCommitAnimationInProgress = false;
+ mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
+ startSystemAnimation();
+ } else if (touchTracker == mCurrentTracker) {
// Only start the back navigation if no other gesture is being processed. Otherwise,
- // the back navigation will be started once the current gesture has finished.
+ // the back navigation will fall back to legacy back event injection.
startBackNavigation(mCurrentTracker);
}
}
@@ -818,6 +834,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
@VisibleForTesting
void onBackAnimationFinished() {
+ if (!mPostCommitAnimationInProgress) {
+ // This can happen when a post-commit cancel animation was interrupted by a new back
+ // gesture but the timing of interruption was bad such that the back-callback
+ // implementation finished in between the time of the new gesture having started and
+ // the time of the back-callback receiving the new onBackStarted event. Due to the
+ // asynchronous APIs this isn't an unlikely case. To handle this, let's return early.
+ // The back-callback implementation will call onBackAnimationFinished again when it is
+ // done with animating the second gesture.
+ return;
+ }
+ finishBackAnimation();
+ }
+
+ private void finishBackAnimation() {
// Stop timeout runner.
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
mPostCommitAnimationInProgress = false;
@@ -878,6 +908,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
void finishBackNavigation(boolean triggerBack) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
mActiveCallback = null;
+ mApps = null;
mShouldStartOnNextMoveEvent = false;
mOnBackStartDispatched = false;
mPointerPilfered = false;
@@ -914,6 +945,42 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTrackingLatency = false;
}
+ private void startSystemAnimation() {
+ if (mBackNavigationInfo == null) {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Lack of navigation info to start animation.");
+ return;
+ }
+ if (mApps == null) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Not starting animation due to mApps being null.");
+ return;
+ }
+
+ final BackAnimationRunner runner =
+ mShellBackAnimationRegistry.getAnimationRunnerAndInit(mBackNavigationInfo);
+ if (runner == null) {
+ if (mBackAnimationFinishedCallback != null) {
+ try {
+ mBackAnimationFinishedCallback.onAnimationFinished(false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call IBackNaviAnimationController", e);
+ }
+ }
+ return;
+ }
+ mActiveCallback = runner.getCallback();
+
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
+
+ runner.startAnimation(mApps, /*wallpapers*/ null, /*nonApps*/ null,
+ () -> mShellExecutor.execute(this::onBackAnimationFinished));
+
+ if (mApps.length >= 1) {
+ mCurrentTracker.updateStartLocation();
+ BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
+ dispatchOnBackStarted(mActiveCallback, startEvent);
+ }
+ }
+
private void createAdapter() {
IBackAnimationRunner runner =
new IBackAnimationRunner.Stub() {
@@ -926,48 +993,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.execute(
() -> {
endLatencyTracking();
- if (mBackNavigationInfo == null) {
- ProtoLog.e(WM_SHELL_BACK_PREVIEW,
- "Lack of navigation info to start animation.");
- return;
- }
- final BackAnimationRunner runner =
- mShellBackAnimationRegistry.getAnimationRunnerAndInit(
- mBackNavigationInfo);
- if (runner == null) {
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished(false);
- } catch (RemoteException e) {
- Log.w(
- TAG,
- "Failed call IBackNaviAnimationController",
- e);
- }
- }
- return;
- }
- mActiveCallback = runner.getCallback();
mBackAnimationFinishedCallback = finishedCallback;
-
- ProtoLog.d(
- WM_SHELL_BACK_PREVIEW,
- "BackAnimationController: startAnimation()");
- runner.startAnimation(
- apps,
- wallpapers,
- nonApps,
- () ->
- mShellExecutor.execute(
- BackAnimationController.this
- ::onBackAnimationFinished));
-
- if (apps.length >= 1) {
- mCurrentTracker.updateStartLocation();
- BackMotionEvent startEvent =
- mCurrentTracker.createStartEvent(apps[0]);
- dispatchOnBackStarted(mActiveCallback, startEvent);
- }
+ mApps = apps;
+ startSystemAnimation();
// Dispatch the first progress after animation start for
// smoothing the initial animation, instead of waiting for next
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index edf29dd484fc..7561a266c5ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -283,6 +283,11 @@ class CrossActivityBackAnimation @Inject constructor(
private inner class Callback : IOnBackInvokedCallback.Default() {
override fun onBackStarted(backMotionEvent: BackMotionEvent) {
+ // in case we're still animating an onBackCancelled event, let's remove the finish-
+ // callback from the progress animator to prevent calling finishAnimation() before
+ // restarting a new animation
+ progressAnimator.removeOnBackCancelledFinishCallback();
+
startBackAnimation(backMotionEvent)
progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent ->
onGestureProgress(backEvent)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 4b3154190910..cfd9fb613414 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -275,8 +275,6 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
- mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
- mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mBackInProgress = true;
}
float progress = backEvent.getProgress();
@@ -326,6 +324,13 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
+ // in case we're still animating an onBackCancelled event, let's remove the finish-
+ // callback from the progress animator to prevent calling finishAnimation() before
+ // restarting a new animation
+ mProgressAnimator.removeOnBackCancelledFinishCallback();
+
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mProgressAnimator.onBackStarted(backEvent,
CrossTaskBackAnimation.this::onGestureProgress);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index 5254ff466123..fcf500a60166 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -285,6 +285,11 @@ public class CustomizeActivityAnimation extends ShellBackAnimation {
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
+ // in case we're still animating an onBackCancelled event, let's remove the finish-
+ // callback from the progress animator to prevent calling finishAnimation() before
+ // restarting a new animation
+ mProgressAnimator.removeOnBackCancelledFinishCallback();
+
mProgressAnimator.onBackStarted(backEvent,
CustomizeActivityAnimation.this::onGestureProgress);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6a1a62ea30a1..d60f5a631044 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
@@ -840,8 +841,11 @@ public class PipTransition extends PipTransitionController {
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
&& !change.getContainer().equals(mCurrentPipTaskToken)) {
// We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
- // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
- if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) {
+ // that enter PiP instantly on opening, mostly from CTS/Flicker tests).
+ // TRANSIT_TO_FRONT, though uncommon with triggering PiP, should semantically also
+ // be allowed to animate if the task in question is pinned already - see b/308054074.
+ if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN
+ || transitType == TRANSIT_TO_FRONT) {
return true;
}
// This can happen if the request to enter PIP happens when we are collecting for
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 703eb199f260..2919782a758a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -409,6 +409,32 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void gestureNotQueued_WhenPreviousGestureIsPostCommitCancelling()
+ throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
+
+ doStartEvents(0, 100);
+ simulateRemoteAnimationStart();
+ releaseBackGesture();
+
+ // Check that back cancellation is dispatched.
+ verify(mAnimatorCallback).onBackCancelled();
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+
+ reset(mAnimatorCallback);
+ reset(mBackAnimationRunner);
+
+ // Verify that a new start event is dispatched if a new gesture is started during the
+ // post-commit cancel phase
+ triggerBackGesture();
+ verify(mAnimatorCallback).onBackStarted(any());
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ }
+
+ @Test
public void acceptsGesture_transitionTimeout() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 7e26577e96d4..8932e60048e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -134,6 +134,31 @@ public class BackProgressAnimatorTest {
assertEquals(0, cancelCallbackCalled.getCount());
}
+ @Test
+ public void testCancelFinishCallbackNotInvokedWhenRemoved() throws InterruptedException {
+ // Give the animator some progress.
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackProgressed(backEvent));
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+ assertNotNull(mReceivedBackEvent);
+
+ // call onBackCancelled (which animates progress to 0 before invoking the finishCallback)
+ CountDownLatch finishCallbackCalled = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mProgressAnimator.onBackCancelled(finishCallbackCalled::countDown));
+
+ // remove onBackCancelled finishCallback (while progress is still animating to 0)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mProgressAnimator.removeOnBackCancelledFinishCallback());
+
+ // call reset (which triggers the finishCallback invocation, if one is present)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset());
+
+ // verify that finishCallback is not invoked
+ assertEquals(1, finishCallbackCalled.getCount());
+ }
+
private void onGestureProgress(BackEvent backEvent) {
if (mTargetProgress == backEvent.getProgress()) {
mReceivedBackEvent = backEvent;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index cda94440affa..341c3e8cf373 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -78,13 +78,13 @@ cc_defaults {
include_dirs: [
"external/skia/include/private",
"external/skia/src/core",
- "external/skia/src/utils",
],
target: {
android: {
include_dirs: [
"external/skia/src/image",
+ "external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
],
@@ -529,9 +529,7 @@ cc_defaults {
"effects/GainmapRenderer.cpp",
"pipeline/skia/BackdropFilterDrawable.cpp",
"pipeline/skia/HolePunch.cpp",
- "pipeline/skia/SkiaCpuPipeline.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
- "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/StretchMask.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",
@@ -570,7 +568,6 @@ cc_defaults {
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
- "LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
"Mesh.cpp",
@@ -607,9 +604,9 @@ cc_defaults {
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
- "pipeline/skia/SkiaGpuPipeline.cpp",
"pipeline/skia/SkiaMemoryTracer.cpp",
"pipeline/skia/SkiaOpenGLPipeline.cpp",
+ "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaProfileRenderer.cpp",
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VkFunctorDrawable.cpp",
@@ -633,6 +630,7 @@ cc_defaults {
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
"Layer.cpp",
+ "LayerUpdateQueue.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index c1510d96461f..ec53070f6cb8 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -242,7 +242,7 @@ enum class ProfileType { None, Console, Bars };
enum class OverdrawColorSet { Default = 0, Deuteranomaly };
-enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 };
+enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
enum class StretchEffectBehavior {
ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
deleted file mode 100644
index 9b8373cea66d..000000000000
--- a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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 "pipeline/skia/SkiaCpuPipeline.h"
-
-#include <system/window.h>
-
-#include "DeviceInfo.h"
-#include "LightingInfo.h"
-#include "renderthread/Frame.h"
-#include "utils/Color.h"
-
-using namespace android::uirenderer::renderthread;
-
-namespace android {
-namespace uirenderer {
-namespace skiapipeline {
-
-void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
- // Render all layers that need to be updated, in order.
- for (size_t i = 0; i < layers.entries().size(); i++) {
- renderLayerImpl(layers.entries()[i].renderNode.get(), layers.entries()[i].damage);
- }
-}
-
-// If the given node didn't have a layer surface, or had one of the wrong size, this method
-// creates a new one and returns true. Otherwise does nothing and returns false.
-bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node,
- const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) {
- // compute the size of the surface (i.e. texture) to be allocated for this layer
- const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
- const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
-
- SkSurface* layer = node->getLayerSurface();
- if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
- SkImageInfo info;
- info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
- kPremul_SkAlphaType, getSurfaceColorSpace());
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
- node->setLayerSurface(SkSurfaces::Raster(info, &props));
- if (node->getLayerSurface()) {
- // update the transform in window of the layer to reset its origin wrt light source
- // position
- Matrix4 windowTransform;
- damageAccumulator.computeCurrentTransform(&windowTransform);
- node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
- } else {
- String8 cachesOutput;
- mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
- &mRenderThread.renderState());
- ALOGE("%s", cachesOutput.c_str());
- if (errorHandler) {
- std::ostringstream err;
- err << "Unable to create layer for " << node->getName();
- const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
- err << ", size " << info.width() << "x" << info.height() << " max size "
- << maxTextureSize << " color type " << (int)info.colorType() << " has context "
- << (int)(mRenderThread.getGrContext() != nullptr);
- errorHandler->onError(err.str());
- }
- }
- return true;
- }
- return false;
-}
-
-MakeCurrentResult SkiaCpuPipeline::makeCurrent() {
- return MakeCurrentResult::AlreadyCurrent;
-}
-
-Frame SkiaCpuPipeline::getFrame() {
- return Frame(mSurface->width(), mSurface->height(), 0);
-}
-
-IRenderPipeline::DrawResult SkiaCpuPipeline::draw(
- const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
- LightingInfo::updateLighting(lightGeometry, lightInfo);
- renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface,
- SkMatrix::I());
- return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
-}
-
-bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
- if (surface) {
- ANativeWindowBuffer* buffer;
- surface->dequeueBuffer(surface, &buffer, nullptr);
- int width, height;
- surface->query(surface, NATIVE_WINDOW_WIDTH, &width);
- surface->query(surface, NATIVE_WINDOW_HEIGHT, &height);
- SkImageInfo imageInfo =
- SkImageInfo::Make(width, height, mSurfaceColorType,
- SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace);
- size_t widthBytes = width * imageInfo.bytesPerPixel();
- void* pixels = buffer->reserved[0];
- mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes);
- } else {
- mSurface = sk_sp<SkSurface>();
- }
- return true;
-}
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
deleted file mode 100644
index 5a1014c2c2de..000000000000
--- a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include "pipeline/skia/SkiaPipeline.h"
-
-namespace android {
-
-namespace uirenderer {
-namespace skiapipeline {
-
-class SkiaCpuPipeline : public SkiaPipeline {
-public:
- SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
- ~SkiaCpuPipeline() {}
-
- bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
- void unpinImages() override {}
-
- // If the given node didn't have a layer surface, or had one of the wrong size, this method
- // creates a new one and returns true. Otherwise does nothing and returns false.
- bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) override;
- void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
- void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
- bool hasHardwareBuffer() override { return false; }
-
- renderthread::MakeCurrentResult makeCurrent() override;
- renderthread::Frame getFrame() override;
- renderthread::IRenderPipeline::DrawResult draw(
- const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const renderthread::HardwareBufferRenderParams& bufferParams,
- std::mutex& profilerLock) override;
- bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
- const SkRect& screenDirty, FrameInfo* currentFrameInfo,
- bool* requireSwap) override {
- return false;
- }
- DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
- bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
- [[nodiscard]] android::base::unique_fd flush() override {
- return android::base::unique_fd(-1);
- };
- void onStop() override {}
- bool isSurfaceReady() override { return mSurface.get() != nullptr; }
- bool isContextReady() override { return true; }
-
- const SkM44& getPixelSnapMatrix() const override {
- static const SkM44 sSnapMatrix = SkM44();
- return sSnapMatrix;
- }
-
-private:
- sk_sp<SkSurface> mSurface;
-};
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
deleted file mode 100644
index cd9daf437bdb..000000000000
--- a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2016 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 "pipeline/skia/SkiaGpuPipeline.h"
-
-#include <SkImageAndroid.h>
-#include <gui/TraceUtils.h>
-#include <include/android/SkSurfaceAndroid.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-
-using namespace android::uirenderer::renderthread;
-
-namespace android {
-namespace uirenderer {
-namespace skiapipeline {
-
-SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {}
-
-SkiaGpuPipeline::~SkiaGpuPipeline() {
- unpinImages();
-}
-
-void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
- sk_sp<GrDirectContext> cachedContext;
-
- // Render all layers that need to be updated, in order.
- for (size_t i = 0; i < layers.entries().size(); i++) {
- RenderNode* layerNode = layers.entries()[i].renderNode.get();
- renderLayerImpl(layerNode, layers.entries()[i].damage);
- // cache the current context so that we can defer flushing it until
- // either all the layers have been rendered or the context changes
- GrDirectContext* currentContext =
- GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
- if (cachedContext.get() != currentContext) {
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers (context changed)");
- cachedContext->flushAndSubmit();
- }
- cachedContext.reset(SkSafeRef(currentContext));
- }
- }
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers");
- cachedContext->flushAndSubmit();
- }
-}
-
-// If the given node didn't have a layer surface, or had one of the wrong size, this method
-// creates a new one and returns true. Otherwise does nothing and returns false.
-bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node,
- const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) {
- // compute the size of the surface (i.e. texture) to be allocated for this layer
- const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
- const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
-
- SkSurface* layer = node->getLayerSurface();
- if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
- SkImageInfo info;
- info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
- kPremul_SkAlphaType, getSurfaceColorSpace());
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
- SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
- if (node->getLayerSurface()) {
- // update the transform in window of the layer to reset its origin wrt light source
- // position
- Matrix4 windowTransform;
- damageAccumulator.computeCurrentTransform(&windowTransform);
- node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
- } else {
- String8 cachesOutput;
- mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
- &mRenderThread.renderState());
- ALOGE("%s", cachesOutput.c_str());
- if (errorHandler) {
- std::ostringstream err;
- err << "Unable to create layer for " << node->getName();
- const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
- err << ", size " << info.width() << "x" << info.height() << " max size "
- << maxTextureSize << " color type " << (int)info.colorType() << " has context "
- << (int)(mRenderThread.getGrContext() != nullptr);
- errorHandler->onError(err.str());
- }
- }
- return true;
- }
- return false;
-}
-
-bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
- if (!mRenderThread.getGrContext()) {
- ALOGD("Trying to pin an image with an invalid GrContext");
- return false;
- }
- for (SkImage* image : mutableImages) {
- if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
- mPinnedImages.emplace_back(sk_ref_sp(image));
- } else {
- return false;
- }
- }
- return true;
-}
-
-void SkiaGpuPipeline::unpinImages() {
- for (auto& image : mPinnedImages) {
- skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
- }
- mPinnedImages.clear();
-}
-
-void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- GrDirectContext* context = thread.getGrContext();
- if (context && !bitmap->isHardware()) {
- ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
- auto image = bitmap->makeImage();
- if (image.get()) {
- skgpu::ganesh::PinAsTexture(context, image.get());
- skgpu::ganesh::UnpinTexture(context, image.get());
- // A submit is necessary as there may not be a frame coming soon, so without a call
- // to submit these texture uploads can just sit in the queue building up until
- // we run out of RAM
- context->flushAndSubmit();
- }
- }
-}
-
-sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams) {
- auto bufferColorSpace = bufferParams.getColorSpace();
- if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
- !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
- mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
- bufferColorSpace, nullptr, true);
- mBufferColorSpace = bufferColorSpace;
- }
- return mBufferSurface;
-}
-
-void SkiaGpuPipeline::dumpResourceCacheUsage() const {
- int resources;
- size_t bytes;
- mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
- size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
-
- SkString log("Resource Cache Usage:\n");
- log.appendf("%8d items\n", resources);
- log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
- bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
-
- ALOGD("%s", log.c_str());
-}
-
-void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
- if (mHardwareBuffer) {
- AHardwareBuffer_release(mHardwareBuffer);
- mHardwareBuffer = nullptr;
- }
-
- if (buffer) {
- AHardwareBuffer_acquire(buffer);
- mHardwareBuffer = buffer;
- }
-}
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index e4b1f916b4d6..c8d598702a7c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-#include "pipeline/skia/SkiaOpenGLPipeline.h"
+#include "SkiaOpenGLPipeline.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
#include <cutils/properties.h>
#include <gui/TraceUtils.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/gl/GrGLTypes.h>
#include <strings.h>
#include "DeferredLayerUpdater.h"
#include "FrameInfo.h"
+#include "LayerDrawable.h"
#include "LightingInfo.h"
+#include "SkiaPipeline.h"
+#include "SkiaProfileRenderer.h"
#include "hwui/Bitmap.h"
-#include "pipeline/skia/LayerDrawable.h"
-#include "pipeline/skia/SkiaGpuPipeline.h"
-#include "pipeline/skia/SkiaProfileRenderer.h"
#include "private/hwui/DrawGlInfo.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
@@ -47,7 +47,7 @@ namespace uirenderer {
namespace skiapipeline {
SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
- : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) {
+ : SkiaPipeline(thread), mEglManager(thread.eglManager()) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 6e7478288777..ebe8b6e15d44 100644
--- a/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -19,7 +19,7 @@
#include <EGL/egl.h>
#include <system/window.h>
-#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "SkiaPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
@@ -30,7 +30,7 @@ class Bitmap;
namespace uirenderer {
namespace skiapipeline {
-class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
+class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback {
public:
SkiaOpenGLPipeline(renderthread::RenderThread& thread);
virtual ~SkiaOpenGLPipeline();
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 2cfdd3fb0315..99469d1e3628 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-#include "pipeline/skia/SkiaPipeline.h"
+#include "SkiaPipeline.h"
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
@@ -37,9 +40,6 @@
#include <SkTypeface.h>
#include <android-base/properties.h>
#include <gui/TraceUtils.h>
-#include <include/android/SkSurfaceAndroid.h>
-#include <include/encode/SkPngEncoder.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <unistd.h>
#include <sstream>
@@ -62,13 +62,37 @@ SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
setSurfaceColorProperties(mColorMode);
}
-SkiaPipeline::~SkiaPipeline() {}
+SkiaPipeline::~SkiaPipeline() {
+ unpinImages();
+}
void SkiaPipeline::onDestroyHardwareResources() {
unpinImages();
mRenderThread.cacheManager().trimStaleResources();
}
+bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
+ if (!mRenderThread.getGrContext()) {
+ ALOGD("Trying to pin an image with an invalid GrContext");
+ return false;
+ }
+ for (SkImage* image : mutableImages) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
+ mPinnedImages.emplace_back(sk_ref_sp(image));
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+void SkiaPipeline::unpinImages() {
+ for (auto& image : mPinnedImages) {
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
+ }
+ mPinnedImages.clear();
+}
+
void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const LightInfo& lightInfo) {
@@ -78,53 +102,136 @@ void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
layerUpdateQueue->clear();
}
-void SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) {
- // only schedule repaint if node still on layer - possible it may have been
- // removed during a dropped frame, but layers may still remain scheduled so
- // as not to lose info on what portion is damaged
- if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
- return;
- }
- SkASSERT(layerNode->getLayerSurface());
- SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
- if (!displayList || displayList->isEmpty()) {
- ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
- return;
- }
+void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ sk_sp<GrDirectContext> cachedContext;
- SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ continue;
+ }
+ SkASSERT(layerNode->getLayerSurface());
+ SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
+ if (!displayList || displayList->isEmpty()) {
+ ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+ return;
+ }
- int saveCount = layerCanvas->save();
- SkASSERT(saveCount == 1);
+ const Rect& layerDamage = layers.entries()[i].damage;
- layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
- // TODO: put localized light center calculation and storage to a drawable related code.
- // It does not seem right to store something localized in a global state
- // fix here and in recordLayers
- const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
- Vector3 transformedLightCenter(savedLightCenter);
- // map current light center into RenderNode's coordinate space
- layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
- LightingInfo::setLightCenterRaw(transformedLightCenter);
-
- const RenderProperties& properties = layerNode->properties();
- const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
- if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
- return;
- }
+ int saveCount = layerCanvas->save();
+ SkASSERT(saveCount == 1);
- ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
- bounds.height());
+ layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
- layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
- layerCanvas->clear(SK_ColorTRANSPARENT);
+ // TODO: put localized light center calculation and storage to a drawable related code.
+ // It does not seem right to store something localized in a global state
+ // fix here and in recordLayers
+ const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+ Vector3 transformedLightCenter(savedLightCenter);
+ // map current light center into RenderNode's coordinate space
+ layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+ LightingInfo::setLightCenterRaw(transformedLightCenter);
- RenderNodeDrawable root(layerNode, layerCanvas, false);
- root.forceDraw(layerCanvas);
- layerCanvas->restoreToCount(saveCount);
+ const RenderProperties& properties = layerNode->properties();
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+ return;
+ }
- LightingInfo::setLightCenterRaw(savedLightCenter);
+ ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+ bounds.height());
+
+ layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+ layerCanvas->clear(SK_ColorTRANSPARENT);
+
+ RenderNodeDrawable root(layerNode, layerCanvas, false);
+ root.forceDraw(layerCanvas);
+ layerCanvas->restoreToCount(saveCount);
+
+ LightingInfo::setLightCenterRaw(savedLightCenter);
+
+ // cache the current context so that we can defer flushing it until
+ // either all the layers have been rendered or the context changes
+ GrDirectContext* currentContext =
+ GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
+ if (cachedContext.get() != currentContext) {
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers (context changed)");
+ cachedContext->flushAndSubmit();
+ }
+ cachedContext.reset(SkSafeRef(currentContext));
+ }
+ }
+
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers");
+ cachedContext->flushAndSubmit();
+ }
+}
+
+bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkASSERT(mRenderThread.getGrContext() != nullptr);
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+ GrDirectContext* context = thread.getGrContext();
+ if (context && !bitmap->isHardware()) {
+ ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
+ auto image = bitmap->makeImage();
+ if (image.get()) {
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
+ // A submit is necessary as there may not be a frame coming soon, so without a call
+ // to submit these texture uploads can just sit in the queue building up until
+ // we run out of RAM
+ context->flushAndSubmit();
+ }
+ }
}
static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
@@ -492,6 +599,45 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip,
}
}
+void SkiaPipeline::dumpResourceCacheUsage() const {
+ int resources;
+ size_t bytes;
+ mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
+ size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
+
+ SkString log("Resource Cache Usage:\n");
+ log.appendf("%8d items\n", resources);
+ log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
+ bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
+
+ ALOGD("%s", log.c_str());
+}
+
+void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+}
+
+sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams) {
+ auto bufferColorSpace = bufferParams.getColorSpace();
+ if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+ !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
+ mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+ bufferColorSpace, nullptr, true);
+ mBufferColorSpace = bufferColorSpace;
+ }
+ return mBufferSurface;
+}
+
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mColorMode = colorMode;
switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index f9d37b924321..befee8989383 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -42,9 +42,18 @@ public:
void onDestroyHardwareResources() override;
+ bool pinImages(std::vector<SkImage*>& mutableImages) override;
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override;
+
void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
bool opaque, const LightInfo& lightInfo) override;
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+
void setSurfaceColorProperties(ColorMode colorMode) override;
SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
@@ -54,8 +63,9 @@ public:
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform);
- void renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage);
- virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0;
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
+
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
// Sets the recording callback to the provided function and the recording mode
// to CallbackAPI
@@ -65,11 +75,19 @@ public:
mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
}
+ virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
+ bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
void setTargetSdrHdrRatio(float ratio) override;
protected:
+ sk_sp<SkSurface> getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams);
+ void dumpResourceCacheUsage() const;
+
renderthread::RenderThread& mRenderThread;
+ AHardwareBuffer* mHardwareBuffer = nullptr;
sk_sp<SkSurface> mBufferSurface = nullptr;
sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
@@ -107,6 +125,8 @@ private:
// Set up a multi frame capture.
bool setupMultiFrameCapture();
+ std::vector<sk_sp<SkImage>> mPinnedImages;
+
// Block of properties used only for debugging to record a SkPicture and save it in a file.
// There are three possible ways of recording drawing commands.
enum class CaptureMode {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index d06dba05ee88..fd0a8e06f39c 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "pipeline/skia/SkiaVulkanPipeline.h"
+#include "SkiaVulkanPipeline.h"
#include <GrDirectContext.h>
#include <GrTypes.h>
@@ -28,10 +28,10 @@
#include "DeferredLayerUpdater.h"
#include "LightingInfo.h"
#include "Readback.h"
-#include "pipeline/skia/ShaderCache.h"
-#include "pipeline/skia/SkiaGpuPipeline.h"
-#include "pipeline/skia/SkiaProfileRenderer.h"
-#include "pipeline/skia/VkInteropFunctorDrawable.h"
+#include "ShaderCache.h"
+#include "SkiaPipeline.h"
+#include "SkiaProfileRenderer.h"
+#include "VkInteropFunctorDrawable.h"
#include "renderstate/RenderState.h"
#include "renderthread/Frame.h"
#include "renderthread/IRenderPipeline.h"
@@ -42,8 +42,7 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
- : SkiaGpuPipeline(thread) {
+SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 0d30df48baee..624eaa51a584 100644
--- a/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -17,7 +17,7 @@
#pragma once
#include "SkRefCnt.h"
-#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "SkiaPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/VulkanManager.h"
@@ -30,7 +30,7 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
+class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback {
public:
explicit SkiaVulkanPipeline(renderthread::RenderThread& thread);
virtual ~SkiaVulkanPipeline();
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
deleted file mode 100644
index 9159eae46065..000000000000
--- a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#pragma once
-
-#include "pipeline/skia/SkiaPipeline.h"
-
-namespace android {
-namespace uirenderer {
-namespace skiapipeline {
-
-class SkiaGpuPipeline : public SkiaPipeline {
-public:
- SkiaGpuPipeline(renderthread::RenderThread& thread);
- virtual ~SkiaGpuPipeline();
-
- virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
-
- // If the given node didn't have a layer surface, or had one of the wrong size, this method
- // creates a new one and returns true. Otherwise does nothing and returns false.
- bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) override;
-
- bool pinImages(std::vector<SkImage*>& mutableImages) override;
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
- void unpinImages() override;
- void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
- void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override;
- bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
-
- static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
-
-protected:
- sk_sp<SkSurface> getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams);
- void dumpResourceCacheUsage() const;
-
- AHardwareBuffer* mHardwareBuffer = nullptr;
-
-private:
- std::vector<sk_sp<SkImage>> mPinnedImages;
-};
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
deleted file mode 120000
index 4fb4784f9f60..000000000000
--- a/libs/hwui/platform/host/android/api-level.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../bionic/libc/include/android/api-level.h \ No newline at end of file
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
deleted file mode 100644
index a71726585081..000000000000
--- a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#pragma once
-
-#include "pipeline/skia/SkiaPipeline.h"
-#include "renderthread/Frame.h"
-
-namespace android {
-namespace uirenderer {
-namespace skiapipeline {
-
-class SkiaGpuPipeline : public SkiaPipeline {
-public:
- SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
- ~SkiaGpuPipeline() {}
-
- bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
- void unpinImages() override {}
-
- // If the given node didn't have a layer surface, or had one of the wrong size, this method
- // creates a new one and returns true. Otherwise does nothing and returns false.
- bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) override {
- return false;
- }
- void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {}
- void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
- bool hasHardwareBuffer() override { return false; }
-
- renderthread::MakeCurrentResult makeCurrent() override {
- return renderthread::MakeCurrentResult::Failed;
- }
- renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); }
- renderthread::IRenderPipeline::DrawResult draw(
- const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const renderthread::HardwareBufferRenderParams& bufferParams,
- std::mutex& profilerLock) override {
- return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)};
- }
- bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
- const SkRect& screenDirty, FrameInfo* currentFrameInfo,
- bool* requireSwap) override {
- return false;
- }
- DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
- bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override {
- return false;
- }
- [[nodiscard]] android::base::unique_fd flush() override {
- return android::base::unique_fd(-1);
- };
- void onStop() override {}
- bool isSurfaceReady() override { return false; }
- bool isContextReady() override { return false; }
-
- const SkM44& getPixelSnapMatrix() const override {
- static const SkM44 sSnapMatrix = SkM44();
- return sSnapMatrix;
- }
- static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {}
-};
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
deleted file mode 100644
index d54caef45bb5..000000000000
--- a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#pragma once
-
-#include "pipeline/skia/SkiaGpuPipeline.h"
-
-namespace android {
-
-namespace uirenderer {
-namespace skiapipeline {
-
-class SkiaVulkanPipeline : public SkiaGpuPipeline {
-public:
- SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
-
- static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
-};
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 984916cb3986..abf64d099935 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,8 +35,8 @@
#include "Properties.h"
#include "RenderThread.h"
#include "hwui/Canvas.h"
-#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
+#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "thread/CommonPool.h"
#include "utils/GLUtils.h"
@@ -108,7 +108,7 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor)
}
void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap);
+ skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
}
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index ee1d1f8789d9..b8c3a4de2bd4 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -30,6 +30,8 @@
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
+class GrDirectContext;
+
struct ANativeWindow;
namespace android {
@@ -92,6 +94,7 @@ public:
virtual void setSurfaceColorProperties(ColorMode colorMode) = 0;
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
+ virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 913af8ac3474..f6c57927cc85 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -16,22 +16,18 @@
#include "Color.h"
-#include <ui/ColorSpace.h>
-#include <utils/Log.h>
-
-#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows
+#include <Properties.h>
#include <android/hardware_buffer.h>
#include <android/native_window.h>
-#endif
+#include <ui/ColorSpace.h>
+#include <utils/Log.h>
#include <algorithm>
#include <cmath>
-#include <Properties.h>
namespace android {
namespace uirenderer {
-#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows
static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t format,
sk_sp<SkColorSpace> colorSpace) {
SkColorType colorType = kUnknown_SkColorType;
@@ -121,7 +117,6 @@ SkColorType BufferFormatToColorType(uint32_t format) {
return kUnknown_SkColorType;
}
}
-#endif
namespace {
static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 0fd61c7b990b..08f1c9300c30 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -92,7 +92,6 @@ static constexpr float EOCF_sRGB(float srgb) {
return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f);
}
-#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows
SkImageInfo ANativeWindowToImageInfo(const ANativeWindow_Buffer& buffer,
sk_sp<SkColorSpace> colorSpace);
@@ -101,7 +100,6 @@ SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc,
uint32_t ColorTypeToBufferFormat(SkColorType colorType);
SkColorType BufferFormatToColorType(uint32_t bufferFormat);
-#endif
ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 6a32c5a71999..a63453d655e2 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -148,8 +148,9 @@ void SpriteController::doUpdateSprites() {
if (update.state.wantSurfaceVisible()) {
int32_t desiredWidth = update.state.icon.width();
int32_t desiredHeight = update.state.icon.height();
- if (update.state.surfaceWidth < desiredWidth
- || update.state.surfaceHeight < desiredHeight) {
+ // TODO(b/331260947): investigate using a larger surface width with smaller sprites.
+ if (update.state.surfaceWidth != desiredWidth ||
+ update.state.surfaceHeight != desiredHeight) {
needApplyTransaction = true;
update.state.surfaceControl->updateDefaultBufferSize(desiredWidth, desiredHeight);
diff --git a/media/OWNERS b/media/OWNERS
index 2e9276d73392..1e5a458a5bff 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -4,6 +4,7 @@ elaurent@google.com
essick@google.com
etalvala@google.com
hunga@google.com
+jchowdhary@google.com
jmtrivi@google.com
jsharkey@android.com
lajos@google.com
diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java
index 85a653c9d80c..b5cae2c5ef10 100644
--- a/media/java/android/media/AudioDescriptor.java
+++ b/media/java/android/media/AudioDescriptor.java
@@ -99,7 +99,7 @@ public class AudioDescriptor implements Parcelable {
* When encapsulation is required, only playback with {@link android.media.AudioTrack} API is
* supported. But playback with {@link android.media.MediaPlayer} is not.
* When an encapsulation type is required, the {@link AudioFormat} encoding selected when
- * creating the {@link AudioTrack} must match the encapsulation type, e.g
+ * creating the {@link AudioTrack} must match the encapsulation type, e.g.
* AudioFormat#ENCODING_IEC61937 for AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937.
*
* @return an integer representing the encapsulation type
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 1fe3c2ecec29..63b45387f040 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3465,7 +3465,7 @@ public class AudioManager {
/* modes for setMode/getMode/setRoute/getRoute */
/**
- * Audio harware modes.
+ * Audio hardware modes.
*/
/**
* Invalid audio mode.
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 80b2be2567a7..6d4cc3a9ca44 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,13 +194,13 @@ package android.nfc {
package android.nfc.cardemulation {
public final class CardEmulation {
- method @Deprecated public boolean categoryAllowsForegroundPreference(String);
+ method public boolean categoryAllowsForegroundPreference(String);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
- method @Deprecated public int getSelectionModeForCategory(String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method public int getSelectionModeForCategory(String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 67697a429a32..de9eada18104 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -36,6 +36,7 @@ import android.nfc.Constants;
import android.nfc.Flags;
import android.nfc.INfcCardEmulation;
import android.nfc.NfcAdapter;
+import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -271,30 +272,31 @@ public final class CardEmulation {
}
/**
+ * <p>
* Returns whether the user has allowed AIDs registered in the
* specified category to be handled by a service that is preferred
* by the foreground application, instead of by a pre-configured default.
*
* Foreground applications can set such preferences using the
* {@link #setPreferredService(Activity, ComponentName)} method.
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this method will always
+ * return true.
*
* @param category The category, e.g. {@link #CATEGORY_PAYMENT}
* @return whether AIDs in the category can be handled by a service
* specified by the foreground app.
- *
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will be done in a AID
- * category agnostic manner.
*/
@SuppressWarnings("NonUserGetterCalled")
- @Deprecated
public boolean categoryAllowsForegroundPreference(String category) {
Context contextAsUser = mContext.createContextAsUser(
UserHandle.of(UserHandle.myUserId()), 0);
+
RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
return true;
}
+
if (CATEGORY_PAYMENT.equals(category)) {
boolean preferForeground = false;
try {
@@ -319,14 +321,14 @@ public final class CardEmulation {
* every time what service they would like to use in this category.
* <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
* to pick a service if there is a conflict.
+ *
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default service defined
+ * by the holder of {@link android.app.role.RoleManager#ROLE_WALLET} and is category agnostic.
+ *
* @param category The category, for example {@link #CATEGORY_PAYMENT}
* @return the selection mode for the passed in category
- *
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will be done in a AID
- * category agnostic manner.
*/
- @Deprecated
public int getSelectionModeForCategory(String category) {
if (CATEGORY_PAYMENT.equals(category)) {
boolean paymentRegistered = false;
@@ -919,6 +921,13 @@ public final class CardEmulation {
/**
* Retrieves the route destination for the preferred payment service.
*
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service
+ * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This
+ * will return the route for one of the services registered by the role holder (if any). If
+ * there are multiple services registered, it is unspecified which of those will be used to
+ * determine the route.
+ *
* @return The route destination secure element name of the preferred payment service.
* HCE payment: "Host"
* OffHost payment: 1. String with prefix SIM or prefix eSE string.
@@ -931,15 +940,8 @@ public final class CardEmulation {
* (e.g. eSE/eSE1, eSE2, etc.).
* 2. "OffHost" if the payment service does not specify secure element
* name.
- *
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
- * A payment service will be selected automatically based on registered AIDs. In the case of
- * multiple services that register for the same payment AID, the selection will be done on
- * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
- @Deprecated
@Nullable
public String getRouteDestinationForPreferredPaymentService() {
try {
@@ -981,16 +983,16 @@ public final class CardEmulation {
/**
* Returns a user-visible description of the preferred payment service.
*
- * @return the preferred payment service description
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service
+ * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This
+ * will return the description for one of the services registered by the role holder (if any).
+ * If there are multiple services registered, it is unspecified which of those will be used
+ * to obtain the service description here.
*
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
- * A payment service will be selected automatically based on registered AIDs. In the case of
- * multiple services that register for the same payment AID, the selection will be done on
- * an alphabetical order based on the component names.
+ * @return the preferred payment service description
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
- @Deprecated
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
try {
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 7a8c25bd12ab..1ac8e19c2bc6 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"/>
<uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
<uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index db5ab569535f..d21077ee7c5a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -175,7 +175,8 @@ private fun setupBiometricPrompt(
}
.setAllowedAuthenticators(finalAuthenticators)
.setConfirmationRequired(true)
- // TODO(b/326243754) : Add logo back once new permission privileges sorted out
+ .setLogoBitmap(biometricDisplayInfo.providerIcon)
+ .setLogoDescription(biometricDisplayInfo.providerName)
.setDescription(biometricDisplayInfo.descriptionAboveBiometricButton)
.build()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
index 1695e4f33915..030522d73b26 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -39,11 +39,17 @@ fun SettingsAlertDialogWithIcon(
confirmButton: AlertDialogButton?,
dismissButton: AlertDialogButton?,
title: String?,
+ icon: @Composable (() -> Unit)? = {
+ Icon(
+ Icons.Default.WarningAmber,
+ contentDescription = null
+ )
+ },
text: @Composable (() -> Unit)?,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
- icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) },
+ icon = icon,
modifier = Modifier.width(getDialogWidth()),
confirmButton = {
confirmButton?.let {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 87a7f823edfe..38a3a2aad609 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -270,6 +270,7 @@ public class SecureSettings {
Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.AUDIO_DEVICE_INVENTORY,
+ Settings.Secure.SCREEN_RESOLUTION_MODE,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index edef286b6ac0..252cb8fa8f8b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -427,5 +427,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator(
+ Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 1eb04ac1c181..9e9350b1a17a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -99,6 +99,7 @@ public class SettingsHelper {
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS);
+ sBroadcastOnRestore.add(Settings.Secure.SCREEN_RESOLUTION_MODE);
sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 4603b43b0ab5..46cee6b1cc0a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2112,6 +2112,12 @@ class SettingsProtoDumpUtil {
Settings.Secure.DOUBLE_TAP_TO_WAKE,
SecureSettingsProto.DOUBLE_TAP_TO_WAKE);
+ final long displayToken = p.start(SecureSettingsProto.DISPLAY);
+ dumpSetting(s, p,
+ Settings.Secure.SCREEN_RESOLUTION_MODE,
+ SecureSettingsProto.Display.SCREEN_RESOLUTION_MODE);
+ p.end(displayToken);
+
final long dozeToken = p.start(SecureSettingsProto.DOZE);
dumpSetting(s, p,
Settings.Secure.DOZE_ENABLED,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 617df74b866d..f911269dd06a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -330,6 +330,7 @@ android_library {
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"mockito-target-extended-minus-junit4",
+ "mockito-kotlin2",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"kotlin-test",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 91717dc92c4a..c845ab3a7fb1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -511,13 +511,6 @@ flag {
}
flag {
- name: "enable_contextual_tips_frequency_cap"
- description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime."
- namespace: "systemui"
- bug: "322891421"
-}
-
-flag {
name: "disable_contextual_tips_frequency_check"
description: "Disables frequency capping check for contextual tips."
namespace: "systemui"
@@ -532,13 +525,6 @@ flag {
}
flag {
- name: "disable_contextual_tips_first_30d_check"
- description: "Disables condition check which only show tips within 30 days after phone setup."
- namespace: "systemui"
- bug: "322891421"
-}
-
-flag {
name: "enable_contextual_tips"
description: "Enables showing contextual tips."
namespace: "systemui"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 244861c277c6..a87a8df290b7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -146,7 +146,7 @@ fun SceneScope.QuickSettings(
modifier.fillMaxWidth().layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
// Use the height of the correct view based on the scene it is being composed in
- val height = heightProvider()
+ val height = heightProvider().coerceAtLeast(0)
layout(placeable.width, height) { placeable.placeRelative(0, 0) }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 11c946261816..25c649a7d4fb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -555,7 +555,7 @@ class AnimatableClockView @JvmOverloads constructor(
if (distance > 0) {
// If distance > 0 then we are moving from the left towards the center.
// We need ensure that the glyphs are offset to the initial position.
- glyphOffsets -= dir * distance
+ glyphOffsets[i] -= dir * distance
}
}
invalidate()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
index 2c890f4e6c42..7d7841f0da5b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.dreams.homecontrols
+import android.os.powerManager
import android.service.dream.dreamManager
import com.android.systemui.common.domain.interactor.packageChangeInteractor
import com.android.systemui.controls.dagger.ControlsComponent
@@ -37,6 +38,7 @@ val Kosmos.homeControlsComponentInteractor by
userRepository = fakeUserRepository,
bgScope = applicationCoroutineScope,
systemClock = fakeSystemClock,
+ powerManager = powerManager,
dreamManager = dreamManager,
packageChangeInteractor = packageChangeInteractor,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index 298ce7029f0a..feb72989980c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -20,7 +20,9 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
import android.content.pm.UserInfo
+import android.os.PowerManager
import android.os.UserHandle
+import android.os.powerManager
import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -50,6 +52,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -196,7 +200,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
)
fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds)
// Task fragment becomes empty as a result of the update.
- underTest.onTaskFragmentEmpty()
+ underTest.onDreamEndUnexpectedly()
runCurrent()
verify(dreamManager, never()).startDream()
@@ -240,7 +244,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
)
fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds + 100)
// Task fragment becomes empty as a result of the update.
- underTest.onTaskFragmentEmpty()
+ underTest.onDreamEndUnexpectedly()
runCurrent()
verify(dreamManager, never()).startDream()
@@ -258,6 +262,25 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun testDreamUnexpectedlyEnds_triggersUserActivity() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeSystemClock.setUptimeMillis(100000L)
+ verify(powerManager, never()).userActivity(anyLong(), anyInt(), anyInt())
+
+ // Dream ends unexpectedly
+ underTest.onDreamEndUnexpectedly()
+
+ verify(powerManager)
+ .userActivity(
+ 100000L,
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS
+ )
+ }
+ }
+
private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
val listings =
listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index a9541d962639..eec74efd4751 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -40,6 +40,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -81,6 +82,10 @@ class KeyguardInteractorTest : SysuiTestCase() {
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+ sharedNotificationContainerInteractor = {
+ kosmos.sharedNotificationContainerInteractor
+ },
+ applicationScope = testScope,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 2ccc8b44eff8..9e5f7c9ba648 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -16,15 +16,20 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,14 +38,22 @@ import org.junit.runner.RunWith
class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val underTest = kosmos.notificationsPlaceholderViewModel
+
@Test
- fun onBoundsChanged_setsNotificationContainerBounds() {
- underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
- assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value)
- .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
- assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value)
- .isEqualTo(StackBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
- }
+ @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ fun onBoundsChanged_setsNotificationContainerBounds() =
+ kosmos.testScope.runTest {
+ underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
+ val containerBounds by
+ collectLastValue(kosmos.keyguardInteractor.notificationContainerBounds)
+ val stackBounds by
+ collectLastValue(kosmos.notificationStackAppearanceInteractor.stackBounds)
+ assertThat(containerBounds)
+ .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
+ assertThat(stackBounds)
+ .isEqualTo(StackBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+ }
+
@Test
fun onContentTopChanged_setsContentTop() {
underTest.onContentTopChanged(padding = 5f)
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 f969ee677763..cc7ebe96ba11 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
@@ -498,7 +498,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
showLockscreen()
keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = 1f, bottom = 2f)
+ NotificationContainerBounds(top = 1f, bottom = 52f)
)
runCurrent()
@@ -526,7 +526,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
showLockscreen()
keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = 1f, bottom = 2f)
+ NotificationContainerBounds(top = 1f, bottom = 52f)
)
runCurrent()
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 6de10b405aba..6538725e2374 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -37,4 +37,6 @@
app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
app:ambientShadowAlpha="0.3"
app:removeTextDescent="true"
- app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" />
+ app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding"
+ android:clickable="false"
+/>
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
index dc32a59d86a9..c709e3436cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
@@ -130,15 +130,9 @@ public class DreamClockTimeComplication implements Complication {
}
@Override
- protected void onViewAttached() {
- mView.setOnClickListener(this::onClick);
- }
+ protected void onViewAttached() {}
@Override
protected void onViewDetached() {}
-
- private void onClick(View v) {
- mUiEventLogger.log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
index 17cc8299c309..4d41262a8073 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
@@ -23,7 +23,6 @@ import com.android.internal.logging.UiEventLogger.UiEventEnum
enum class DreamOverlayUiEvent(private val mId: Int) : UiEventEnum {
@UiEvent(doc = "The home controls on the screensaver has been tapped.")
DREAM_HOME_CONTROLS_TAPPED(1212),
- @UiEvent(doc = "The clock on the screensaver has been tapped") DREAM_CLOCK_TAPPED(1440),
@UiEvent(doc = "The weather on the screensaver has been tapped") DREAM_WEATHER_TAPPED(1441);
override fun getId(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 376d3129d8c3..ee8e2059e177 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -81,7 +81,7 @@ constructor(
activity = activity,
onCreateCallback = this::onTaskFragmentCreated,
onInfoChangedCallback = this::onTaskFragmentInfoChanged,
- hide = { finish() }
+ hide = { endDream() }
)
.apply { createTaskFragment() }
@@ -91,11 +91,15 @@ constructor(
private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
if (taskFragmentInfo.isEmpty) {
logger.d("Finishing dream due to TaskFragment being empty")
- finish()
- homeControlsComponentInteractor.onTaskFragmentEmpty()
+ endDream()
}
}
+ private fun endDream() {
+ homeControlsComponentInteractor.onDreamEndUnexpectedly()
+ wakeUp()
+ }
+
private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) {
val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
val componentName = homeControlsComponentInteractor.panelComponent.value
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index f0067dcb7fe2..74452d1980be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.homecontrols.domain.interactor
import android.annotation.SuppressLint
import android.app.DreamManager
import android.content.ComponentName
+import android.os.PowerManager
import android.os.UserHandle
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.domain.interactor.PackageChangeInteractor
@@ -66,6 +67,7 @@ constructor(
userRepository: UserRepository,
private val packageChangeInteractor: PackageChangeInteractor,
private val systemClock: SystemClock,
+ private val powerManager: PowerManager,
private val dreamManager: DreamManager,
@Background private val bgScope: CoroutineScope
) {
@@ -135,7 +137,12 @@ constructor(
private val taskFragmentFinished =
MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- fun onTaskFragmentEmpty() {
+ fun onDreamEndUnexpectedly() {
+ powerManager.userActivity(
+ systemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
+ )
taskFragmentFinished.tryEmit(systemClock.currentTimeMillis())
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 283f1601846b..851eafa1a4df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -29,6 +29,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -46,14 +48,17 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -66,6 +71,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -84,16 +90,33 @@ constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
sceneInteractorProvider: Provider<SceneInteractor>,
private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
+ sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>,
+ @Application applicationScope: CoroutineScope,
) {
// TODO(b/296118689): move to a repository
- private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds())
+ private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
/** Bounds of the notification container. */
- val notificationContainerBounds: StateFlow<NotificationContainerBounds> =
- _sharedNotificationContainerBounds.asStateFlow()
+ val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
+ combine(
+ _notificationPlaceholderBounds,
+ sharedNotificationContainerInteractor.get().configurationBasedDimensions,
+ ) { bounds, cfg ->
+ // We offset the placeholder bounds by the configured top margin to account for
+ // legacy placement behavior within notifications for splitshade.
+ if (MigrateClocksToBlueprint.isEnabled && cfg.useSplitShade) {
+ bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ } else bounds
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = NotificationContainerBounds(),
+ )
+ }
fun setNotificationContainerBounds(position: NotificationContainerBounds) {
- _sharedNotificationContainerBounds.value = position
+ _notificationPlaceholderBounds.value = position
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 2b601cdc012f..edcf97a81ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -25,7 +25,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
@@ -51,21 +50,18 @@ constructor(
* indication area, whichever is higher.
*/
protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) {
- val lockId =
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- R.id.device_entry_icon_view
- } else {
- R.id.lock_icon_view
- }
-
constraintSet.apply {
createBarrier(
R.id.nssl_placeholder_barrier_bottom,
Barrier.TOP,
0,
- *intArrayOf(lockId, R.id.ambient_indication_container)
+ *intArrayOf(
+ R.id.device_entry_icon_view,
+ R.id.lock_icon_view,
+ R.id.ambient_indication_container
+ )
)
- connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP)
+ connect(placeHolderId, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 8cdf60b20786..c1dd992b64cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -171,6 +171,9 @@ public final class NotificationEntry extends ListEntry {
private boolean mIsMarkedForUserTriggeredMovement;
private boolean mIsHeadsUpEntry;
+ private boolean mHasEverBeenGroupSummary;
+ private boolean mHasEverBeenGroupChild;
+
public boolean mRemoteEditImeAnimatingAway;
public boolean mRemoteEditImeVisible;
private boolean mExpandAnimationRunning;
@@ -217,6 +220,26 @@ public final class NotificationEntry extends ListEntry {
mIsDemoted = true;
}
+ /** called when entry is currently a summary of a group */
+ public void markAsGroupSummary() {
+ mHasEverBeenGroupSummary = true;
+ }
+
+ /** whether this entry has ever been marked as a summary */
+ public boolean hasEverBeenGroupSummary() {
+ return mHasEverBeenGroupSummary;
+ }
+
+ /** called when entry is currently a child in a group */
+ public void markAsGroupChild() {
+ mHasEverBeenGroupChild = true;
+ }
+
+ /** whether this entry has ever been marked as a child */
+ public boolean hasEverBeenGroupChild() {
+ return mHasEverBeenGroupChild;
+ }
+
/**
* @param sbn the StatusBarNotification from system server
* @param ranking also from system server
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 7a7b18450b48..9b075a650b48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -49,6 +49,8 @@ import com.android.systemui.statusbar.notification.collection.render.NotifViewBa
import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -273,10 +275,14 @@ public class PreparationCoordinator implements Coordinator {
private void inflateRequiredGroupViews(GroupEntry groupEntry) {
NotificationEntry summary = groupEntry.getSummary();
+ if (summary != null && AsyncGroupHeaderViewInflation.isEnabled()) {
+ summary.markAsGroupSummary();
+ }
List<NotificationEntry> children = groupEntry.getChildren();
inflateRequiredNotifViews(summary);
for (int j = 0; j < children.size(); j++) {
NotificationEntry child = children.get(j);
+ if (AsyncHybridViewInflation.isEnabled()) child.markAsGroupChild();
boolean childShouldBeBound = j < mChildBindCutoff;
if (childShouldBeBound) {
inflateRequiredNotifViews(child);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index bab94b50018e..e70fb6b0fdf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -52,11 +52,8 @@ class NotifUiAdjustment internal constructor(
oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
- // TODO(b/217799515): Here we decide whether to re-inflate the row on every group-status
- // change if we want to keep the single-line view, the following line should be:
- // !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
AsyncHybridViewInflation.isEnabled &&
- oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true
+ !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
AsyncGroupHeaderViewInflation.isEnabled &&
!oldAdjustment.isGroupSummary && newAdjustment.isGroupSummary -> true
else -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index e0e5a3578c31..4c2ef8322731 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -141,7 +141,7 @@ class NotifUiAdjustmentProvider @Inject constructor(
lockscreenUserManager.needsRedaction(entry) ||
(screenshareNotificationHiding() &&
sensitiveNotifProtectionController.shouldProtectNotification(entry)),
- isChildInGroup = entry.sbn.isAppOrSystemGroupChild,
- isGroupSummary = entry.sbn.isAppOrSystemGroupSummary,
+ isChildInGroup = entry.hasEverBeenGroupChild(),
+ isGroupSummary = entry.hasEverBeenGroupSummary(),
)
}
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 ac44b3e4f1f2..5b9eb21bf5e9 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
@@ -414,7 +414,7 @@ public class AmbientState implements Dumpable {
return mLayoutMaxHeight;
}
- public float getTopPadding() {
+ public int getTopPadding() {
return mTopPadding;
}
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 bef26d9958ef..3944c3ae5cdb 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
@@ -201,7 +201,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private float mIntrinsicContentHeight;
private int mPaddingBetweenElements;
private int mMaxTopPadding;
- private int mTopPadding;
private boolean mAnimateNextTopPaddingChange;
private int mBottomPadding;
@VisibleForTesting
@@ -282,7 +281,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
private float mMinTopOverScrollToEscape;
private int mIntrinsicPadding;
- private float mStackTranslation;
private float mTopPaddingOverflow;
private boolean mDontReportNextOverScroll;
private boolean mDontClampNextScroll;
@@ -820,8 +818,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
int y = 0;
drawDebugInfo(canvas, y, Color.RED, /* label= */ "y = " + y);
- y = mTopPadding;
- drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding = " + y);
+ y = getTopPadding();
+ drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y);
y = getLayoutHeight();
drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = " + y);
@@ -1159,7 +1157,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAmbientState.setLayoutHeight(getLayoutHeight());
mAmbientState.setLayoutMaxHeight(mMaxLayoutHeight);
updateAlgorithmLayoutMinHeight();
- mAmbientState.setTopPadding(mTopPadding);
}
private void updateAlgorithmLayoutMinHeight() {
@@ -1252,13 +1249,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
public int getTopPadding() {
- return mTopPadding;
+ return mAmbientState.getTopPadding();
}
private void setTopPadding(int topPadding, boolean animate) {
- if (mTopPadding != topPadding) {
+ if (getTopPadding() != topPadding) {
+ mAmbientState.setTopPadding(topPadding);
boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
- mTopPadding = topPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
if (mAmbientState.isOnKeyguard()
@@ -1303,7 +1300,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void updateStackPosition(boolean listenerNeedsAnimation) {
float topOverscrollAmount = mShouldUseSplitNotificationShade
? getCurrentOverScrollAmount(true /* top */) : 0f;
- final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition
+ mAmbientState.getOverExpansion()
+ topOverscrollAmount
- getCurrentOverScrollAmount(false /* top */);
@@ -1316,7 +1313,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
if (SceneContainerFlag.isEnabled()) {
// stackY should be driven by scene container, not NSSL
- mAmbientState.setStackY(mTopPadding);
+ mAmbientState.setStackY(getTopPadding());
} else {
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
mAmbientState.setStackY(stackY);
@@ -1333,7 +1330,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
final float oldStackHeight = mAmbientState.getStackHeight();
if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
final float endHeight = updateStackEndHeight(
- getHeight(), getEmptyBottomMargin(), mTopPadding);
+ getHeight(), getEmptyBottomMargin(), getTopPadding());
updateStackHeight(endHeight, fraction);
} else {
// Always updateStackHeight to prevent jumps in the stack height when this fraction
@@ -1420,9 +1417,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!appearing) {
translationY = 0;
if (mShouldShowShelfOnly) {
- stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
+ stackHeight = getTopPadding() + mShelf.getIntrinsicHeight();
} else if (mQsFullScreen) {
- int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding;
+ int stackStartPosition = mContentHeight - getTopPadding() + mIntrinsicPadding;
int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
if (stackStartPosition <= stackEndPosition) {
stackHeight = stackEndPosition;
@@ -1451,7 +1448,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
stackHeight = (int) (height - translationY);
if (isHeadsUpTransition() && appearFraction >= 0) {
int topSpacing = mShouldUseSplitNotificationShade
- ? mAmbientState.getStackTopMargin() : mTopPadding;
+ ? mAmbientState.getStackTopMargin() : getTopPadding();
float startPos = mHeadsUpInset - topSpacing;
translationY = MathUtils.lerp(startPos, 0, appearFraction);
}
@@ -1524,7 +1521,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Measured relative to the resting position.
*/
private float getExpandTranslationStart() {
- return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
+ return -getTopPadding() + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
}
/**
@@ -1593,7 +1590,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} else {
appearPosition = mEmptyShadeView.getHeight();
}
- return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
+ return appearPosition + (onKeyguard() ? getTopPadding() : mIntrinsicPadding);
}
/**
@@ -1619,7 +1616,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} else {
appearPosition = mEmptyShadeView.getHeight();
}
- return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
+ return appearPosition + (onKeyguard() ? getTopPadding() : mIntrinsicPadding);
}
private boolean isHeadsUpTransition() {
@@ -1653,12 +1650,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
public float getStackTranslation() {
- return mStackTranslation;
+ return mAmbientState.getStackTranslation();
}
private void setStackTranslation(float stackTranslation) {
- if (stackTranslation != mStackTranslation) {
- mStackTranslation = stackTranslation;
+ if (stackTranslation != getStackTranslation()) {
mAmbientState.setStackTranslation(stackTranslation);
requestChildrenUpdate();
}
@@ -2322,7 +2318,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// 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(mIntrinsicPadding, mTopPadding) + mBottomPadding);
+ mContentHeight =
+ (int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding);
updateScrollability();
clampScrollPosition();
updateStackPosition();
@@ -2800,7 +2797,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (mShouldUseSplitNotificationShade) {
return mSidePaddings;
}
- return mTopPadding - mQsScrollBoundaryPosition;
+ return getTopPadding() - mQsScrollBoundaryPosition;
}
private int getIntrinsicHeight(View view) {
@@ -3859,7 +3856,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// fall through
case android.R.id.accessibilityActionScrollUp:
final int viewportHeight =
- getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
+ getHeight() - mPaddingBottom - getTopPadding() - mPaddingTop
- mShelf.getIntrinsicHeight();
final int targetScrollY = Math.max(0,
Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
@@ -4077,7 +4074,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (row.isChildInGroup()) {
endPosition += row.getNotificationParent().getTranslationY();
}
- int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
+ int layoutEnd = mMaxLayoutHeight + (int) getStackTranslation();
NotificationSection lastSection = getLastVisibleSection();
ExpandableView lastVisibleChild =
lastSection == null ? null : lastSection.getLastVisibleChild();
@@ -4560,7 +4557,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
}
- return touchY > mTopPadding + mStackTranslation;
+ return touchY > getTopPadding() + getStackTranslation();
}
/**
@@ -4943,7 +4940,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
println(pw, "intrinsicContentHeight", mIntrinsicContentHeight);
println(pw, "contentHeight", mContentHeight);
println(pw, "intrinsicPadding", mIntrinsicPadding);
- println(pw, "topPadding", mTopPadding);
+ println(pw, "topPadding", getTopPadding());
println(pw, "bottomPadding", mBottomPadding);
dumpRoundedRectClipping(pw);
println(pw, "requestedClipBounds", mRequestedClipBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c3274477862a..27a708a00cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -173,9 +173,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private static final String TYPE_DISMISS = "dismiss";
/** Volume dialog slider animation. */
private static final String TYPE_UPDATE = "update";
- static final short PROGRESS_HAPTICS_DISABLED = 0;
- static final short PROGRESS_HAPTICS_EAGER = 1;
- static final short PROGRESS_HAPTICS_ANIMATED = 2;
+ static final int PROGRESS_HAPTICS_DISABLED = 0;
+ static final int PROGRESS_HAPTICS_EAGER = 1;
+ static final int PROGRESS_HAPTICS_ANIMATED = 2;
/**
* TODO(b/290612381): remove lingering animations or tolerate them
@@ -851,10 +851,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
}
row.slider = row.view.findViewById(R.id.volume_row_slider);
- if (hapticVolumeSlider()) {
- row.createPlugin(mVibratorHelper, mSystemClock);
- HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
- }
+ addSliderHapticsToRow(row);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
@@ -915,6 +912,23 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
}
}
+ private void addSliderHapticsToRow(VolumeRow row) {
+ if (hapticVolumeSlider()) {
+ row.createPlugin(mVibratorHelper, mSystemClock);
+ HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
+ }
+ }
+
+ @VisibleForTesting void addSliderHapticsToRows() {
+ for (VolumeRow row: mRows) {
+ addSliderHapticsToRow(row);
+ }
+ }
+
+ @VisibleForTesting void removeDismissMessages() {
+ mHandler.removeMessages(H.DISMISS);
+ }
+
private void setRingerMode(int newRingerMode) {
Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode);
incrementManualToggleCount();
@@ -2105,7 +2119,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
}
}
- @VisibleForTesting short progressHapticsForStream(int stream) {
+ @VisibleForTesting int progressHapticsForStream(int stream) {
for (VolumeRow row: mRows) {
if (row.stream == stream) {
return row.mProgressHapticsType;
@@ -2619,7 +2633,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private int animTargetProgress;
private int lastAudibleLevel = 1;
private SeekableSliderHapticPlugin mHapticPlugin;
- private short mProgressHapticsType = PROGRESS_HAPTICS_DISABLED;
+ private int mProgressHapticsType = PROGRESS_HAPTICS_DISABLED;
void setIcon(int iconRes, Resources.Theme theme) {
if (icon != null) {
@@ -2661,7 +2675,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
slider.setOnTouchListener(null);
}
- void deliverOnProgressChangedHaptics(boolean fromUser, int progress, short hapticsType) {
+ void deliverOnProgressChangedHaptics(boolean fromUser, int progress, int hapticsType) {
if (mHapticPlugin == null) return;
mHapticPlugin.onProgressChanged(slider, progress, fromUser);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
index cbbbe5203b24..b9aa4c65be92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
@@ -38,7 +38,6 @@ import com.android.systemui.shared.condition.Monitor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -140,18 +139,4 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase {
assertThat(viewHolder.getView()).isEqualTo(mView);
assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
}
-
- @Test
- public void testClick_logUiEvent() {
- final DreamClockTimeComplication.DreamClockTimeViewController controller =
- new DreamClockTimeComplication.DreamClockTimeViewController(mView, mUiEventLogger);
- controller.onViewAttached();
-
- final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
- ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mView).setOnClickListener(clickListenerCaptor.capture());
-
- clickListenerCaptor.getValue().onClick(mView);
- verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
index 49f7565517da..c782e9d2d98d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.coroutines.collectValues
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
@@ -441,6 +442,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
modules =
[
SysUITestModule::class,
+ BiometricsDomainLayerModule::class,
]
)
interface TestComponent {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
index e9399cc17158..33e9b363915c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
@@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -118,6 +119,7 @@ class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() {
modules =
[
SysUITestModule::class,
+ BiometricsDomainLayerModule::class,
]
)
interface TestComponent {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 1ee26db81826..02f2e16b9570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -197,7 +197,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
shadeRepository,
keyguardTransitionInteractor,
() -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor());
+ () -> mKosmos.getFromGoneTransitionInteractor(),
+ () -> mKosmos.getSharedNotificationContainerInteractor(),
+ mTestScope);
CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index b9451bafec90..ee279d892c1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -223,7 +223,9 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mShadeRepository,
keyguardTransitionInteractor,
() -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor());
+ () -> mKosmos.getFromGoneTransitionInteractor(),
+ () -> mKosmos.getSharedNotificationContainerInteractor(),
+ mTestScope);
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index f9e08fee2120..e54b53225320 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -50,7 +50,6 @@ import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionI
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
@@ -65,6 +64,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
@@ -153,6 +153,8 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
keyguardTransitionInteractor,
{ kosmos.sceneInteractor },
{ kosmos.fromGoneTransitionInteractor },
+ { kosmos.sharedNotificationContainerInteractor },
+ testScope,
)
whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 34eeba05906a..8e6ceccbc14e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -49,7 +49,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@@ -158,17 +157,14 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
@Test
@EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
- fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
+ fun becomeChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
// Given: an Entry that is not child in group
// AsyncHybridViewInflation flag is enabled
- val spySbn = spy(entry.sbn)
- entry.sbn = spySbn
- whenever(spySbn.isAppOrSystemGroupChild).thenReturn(false)
val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
assertThat(oldAdjustment.isChildInGroup).isFalse()
// When: the Entry becomes a group child
- whenever(spySbn.isAppOrSystemGroupChild).thenReturn(true)
+ entry.markAsGroupChild()
val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
assertThat(newAdjustment.isChildInGroup).isTrue()
assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
@@ -179,17 +175,14 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
@Test
@DisableFlags(AsyncHybridViewInflation.FLAG_NAME)
- fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
+ fun becomeChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
// Given: an Entry that is not child in group
// AsyncHybridViewInflation flag is disabled
- val spySbn = spy(entry.sbn)
- entry.sbn = spySbn
- whenever(spySbn.isAppOrSystemGroupChild).thenReturn(false)
val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
assertThat(oldAdjustment.isChildInGroup).isFalse()
// When: the Entry becomes a group child
- whenever(spySbn.isAppOrSystemGroupChild).thenReturn(true)
+ entry.markAsGroupChild()
val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
assertThat(newAdjustment.isChildInGroup).isTrue()
assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
@@ -202,14 +195,11 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
@EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
fun changeIsGroupSummary_needReInflation() {
// Given: an Entry that is not a group summary
- val spySbn = spy(entry.sbn)
- entry.sbn = spySbn
- whenever(spySbn.isAppOrSystemGroupSummary).thenReturn(false)
val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
assertThat(oldAdjustment.isGroupSummary).isFalse()
// When: the Entry becomes a group summary
- whenever(spySbn.isAppOrSystemGroupSummary).thenReturn(true)
+ entry.markAsGroupSummary()
val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
assertThat(newAdjustment.isGroupSummary).isTrue()
assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 7faf5628b40a..b410b33b97d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -21,6 +21,7 @@ import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.collectLastValue
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
@@ -155,7 +156,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
private val bubbles: Bubbles = mock()
- @Component(modules = [SysUITestModule::class])
+ @Component(modules = [SysUITestModule::class, BiometricsDomainLayerModule::class])
@SysUISingleton
interface TestComponent : SysUITestComponent<AlwaysOnDisplayNotificationIconsInteractor> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 995da8192f7b..2c2b1831bcfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -886,7 +886,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
fun shadeClosed_hunShouldHaveFullShadow() {
// Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
// the height of HUN is equal to the height of QQS Panel,
- ambientState.stackTranslation = -ambientState.topPadding
+ ambientState.stackTranslation = (-ambientState.topPadding).toFloat()
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
val childHunView =
@@ -914,7 +914,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
// Given: shade is closed when HUN pops up,
// now drags down the HUN to open shade
- ambientState.stackTranslation = -ambientState.topPadding
+ ambientState.stackTranslation = (-ambientState.topPadding).toFloat()
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
val childHunView =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index d9e9c596ee4a..05fd63e96089 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -178,7 +178,9 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
new FakeShadeRepository(),
keyguardTransitionInteractor,
() -> mKosmos.getSceneInteractor(),
- () -> mKosmos.getFromGoneTransitionInteractor());
+ () -> mKosmos.getFromGoneTransitionInteractor(),
+ () -> mKosmos.getSharedNotificationContainerInteractor(),
+ mTestScope);
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index f63f79ff9835..865b312b6a4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.CoroutineTestScopeModule
import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.collectLastValue
import com.android.systemui.collectValues
import com.android.systemui.communal.dagger.CommunalModule
@@ -60,6 +61,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
[
SysUITestModule::class,
CommunalModule::class,
+ BiometricsDomainLayerModule::class,
]
)
interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index e4b9f102c51c..ae3425678abd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
@@ -66,6 +67,8 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() {
kosmos.keyguardTransitionInteractor,
{ kosmos.sceneInteractor },
{ kosmos.fromGoneTransitionInteractor },
+ { kosmos.sharedNotificationContainerInteractor },
+ testScope,
)
private val keyguardStatusBarInteractor =
KeyguardStatusBarInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 5206db4aa13a..11a53f753b2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -95,7 +95,6 @@ import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -272,50 +271,53 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
@DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() {
- // Initialize the dialog again with haptic sliders disabled
- mDialog.init(0, null);
final State shellState = createShellState();
VolumeDialogController.StreamState musicStreamState =
shellState.states.get(AudioSystem.STREAM_MUSIC);
mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processMessages(1); //Only the SHOW message
+ mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS
// Change the volume two times
musicStreamState.level += 10;
mDialog.onStateChangedH(shellState);
- mAnimatorTestRule.advanceTimeBy(10);
musicStreamState.level += 10;
mDialog.onStateChangedH(shellState);
- // expected: the type of the progress haptics for the stream should be DISABLED
- short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
+ // expected: the type of the latest progress haptics for the stream should be DISABLED
+ int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
+
+ mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss
+ mTestableLooper.processAllMessages();
}
- @Ignore("Causing breakages so ignoring to resolve, b/329099861")
@Test
@EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
- // Initialize the dialog again to create haptic plugins on the rows with the flag enabled
- mDialog.init(0, null);
+ // create haptic plugins on the rows with the flag enabled
+ mDialog.addSliderHapticsToRows();
final State shellState = createShellState();
VolumeDialogController.StreamState musicStreamState =
shellState.states.get(AudioSystem.STREAM_MUSIC);
mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processMessages(1); //Only the SHOW message
+ mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS
// Change the volume two times
musicStreamState.level += 10;
mDialog.onStateChangedH(shellState);
- mAnimatorTestRule.advanceTimeBy(10);
musicStreamState.level += 10;
mDialog.onStateChangedH(shellState);
- // expected: the type of the progress haptics for the stream should be EAGER
- short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
+ // expected: the type of the latest progress haptics for the stream should be EAGER
+ int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type);
+
+ mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss
+ mTestableLooper.processAllMessages();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ec27f48f9570..aabd4e9e79be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -442,7 +442,9 @@ public class BubblesTest extends SysuiTestCase {
shadeRepository,
keyguardTransitionInteractor,
() -> sceneInteractor,
- () -> mKosmos.getFromGoneTransitionInteractor());
+ () -> mKosmos.getFromGoneTransitionInteractor(),
+ () -> mKosmos.getSharedNotificationContainerInteractor(),
+ mTestScope);
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/packages/SystemUI/tests/utils/src/android/os/PowerManagerKosmos.kt
index 4fafbcc4748d..4ddbb457fd6b 100644
--- a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/packages/SystemUI/tests/utils/src/android/os/PowerManagerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,22 +14,9 @@
* limitations under the License.
*/
-#pragma once
+package android.os
-#include "pipeline/skia/SkiaGpuPipeline.h"
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-namespace android {
-
-namespace uirenderer {
-namespace skiapipeline {
-
-class SkiaOpenGLPipeline : public SkiaGpuPipeline {
-public:
- SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
-
- static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
-};
-
-} /* namespace skiapipeline */
-} /* namespace uirenderer */
-} /* namespace android */
+var Kosmos.powerManager by Kosmos.Fixture { mock<PowerManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 00cdc337bc06..e21c76672c1d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -24,15 +24,20 @@ import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor.ConfigurationBasedDimensions
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
/**
* Simply put, I got tired of adding a constructor argument and then having to tweak dozens of
@@ -52,13 +57,33 @@ object KeyguardInteractorFactory {
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
sceneInteractor: SceneInteractor = mock(),
fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
+ sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null,
powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
+ testScope: CoroutineScope = TestScope(),
): WithDependencies {
- // Mock this until the class is replaced by kosmos
- val keyguardTransitionInteractor: KeyguardTransitionInteractor = mock()
+ // Mock these until they are replaced by kosmos
val currentKeyguardStateFlow = MutableSharedFlow<KeyguardState>()
- whenever(keyguardTransitionInteractor.currentKeyguardState)
- .thenReturn(currentKeyguardStateFlow)
+ val keyguardTransitionInteractor =
+ mock<KeyguardTransitionInteractor>().also {
+ whenever(it.currentKeyguardState).thenReturn(currentKeyguardStateFlow)
+ }
+ val configurationDimensionFlow = MutableSharedFlow<ConfigurationBasedDimensions>()
+ configurationDimensionFlow.tryEmit(
+ ConfigurationBasedDimensions(
+ useSplitShade = false,
+ useLargeScreenHeader = false,
+ marginHorizontal = 0,
+ marginBottom = 0,
+ marginTop = 0,
+ marginTopLargeScreen = 0,
+ keyguardSplitShadeTopMargin = 0,
+ )
+ )
+ val sncInteractor =
+ sharedNotificationContainerInteractor
+ ?: mock<SharedNotificationContainerInteractor>().also {
+ whenever(it.configurationBasedDimensions).thenReturn(configurationDimensionFlow)
+ }
return WithDependencies(
repository = repository,
commandQueue = commandQueue,
@@ -79,6 +104,8 @@ object KeyguardInteractorFactory {
keyguardTransitionInteractor = keyguardTransitionInteractor,
powerInteractor = powerInteractor,
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+ sharedNotificationContainerInteractor = { sncInteractor },
+ applicationScope = testScope,
),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index d61bc9f559bb..2a0c01c5c0af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -20,11 +20,13 @@ import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
val Kosmos.keyguardInteractor: KeyguardInteractor by
Kosmos.Fixture {
@@ -39,5 +41,7 @@ val Kosmos.keyguardInteractor: KeyguardInteractor by
keyguardTransitionInteractor = keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+ sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor },
+ applicationScope = testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 67d08f8d6284..1b23296ec4d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -49,6 +49,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -102,6 +103,9 @@ class KosmosJavaAdapter(
val globalActionsInteractor by lazy { kosmos.globalActionsInteractor }
val sceneDataSource by lazy { kosmos.sceneDataSource }
val keyguardClockInteractor by lazy { kosmos.keyguardClockInteractor }
+ val sharedNotificationContainerInteractor by lazy {
+ kosmos.sharedNotificationContainerInteractor
+ }
init {
kosmos.applicationContext = testCase.context
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 997f3af3533e..04b19ffd4dfc 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -24,10 +24,13 @@ flag {
}
flag {
- name: "compute_window_changes_on_a11y"
+ name: "compute_window_changes_on_a11y_v2"
namespace: "accessibility"
description: "Computes accessibility window changes in accessibility instead of wm package."
bug: "322444245"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a57138f9de72..21cc8da22088 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -164,6 +164,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IntPair;
import com.android.internal.util.Preconditions;
+import com.android.modules.expresslog.Counter;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -252,6 +253,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0;
private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1;
+ /**
+ * The counter metric id tracking how many times users add qs shortcut for a11y features.
+ *
+ * <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg.
+ */
+ static final String METRIC_ID_QS_SHORTCUT_ADD = "accessibility.value_qs_shortcut_add";
+
+ /**
+ * The counter metric id tracking how many times users remove qs shortcut for a11y features.
+ *
+ * <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg.
+ */
+ static final String METRIC_ID_QS_SHORTCUT_REMOVE = "accessibility.value_qs_shortcut_remove";
+
private final Context mContext;
@@ -1767,6 +1782,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) {
// TODO(b/314850435): show full device control warning if needed after
// SysUI QS Panel can update live
+ // The user attempts to add QS shortcut in QS Panel, but we don't actually
+ // turn on the shortcut due to lack of full device control permission
+ logMetricForQsShortcutConfiguration(/* enable= */ true, /* numOfFeatures= */ 1);
continue;
}
a11yFeaturesToEnable.add(a11yFeature);
@@ -4183,6 +4201,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
);
if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ int numOfFeatureChanged = Math.abs(currentTargets.size() - validNewTargets.size());
+ logMetricForQsShortcutConfiguration(enable, numOfFeatureChanged);
userState.updateA11yQsTargetLocked(validNewTargets);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
onUserStateChangedLocked(userState);
@@ -6215,7 +6235,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
-
/**
* Bypasses the timeout restriction if volume key shortcut assigned.
*/
@@ -6225,4 +6244,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
/* true */ 1);
}
+
+ /**
+ * Log the metric when the user add/remove qs shortcut for accessibility features. Use the
+ * callingUid to know where the users configure the a11y qs shortcuts.
+ */
+ private void logMetricForQsShortcutConfiguration(boolean enable, int numOfFeatures) {
+ if (numOfFeatures <= 0) {
+ // Skip logging metric if no a11y features are configured
+ return;
+ }
+ String metricId = enable ? METRIC_ID_QS_SHORTCUT_ADD : METRIC_ID_QS_SHORTCUT_REMOVE;
+ Counter.logIncrementWithUid(metricId, Binder.getCallingUid(), numOfFeatures);
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index d30748478741..6007bfd99e7b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -445,7 +445,7 @@ public class AccessibilityWindowManager {
public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) {
synchronized (mLock) {
- if (!Flags.computeWindowChangesOnA11y()) {
+ if (!Flags.computeWindowChangesOnA11yV2()) {
// If the flag is enabled, it's already done in #createWindowInfoListLocked.
updateWindowsByWindowAttributesLocked(windows);
}
@@ -491,7 +491,7 @@ public class AccessibilityWindowManager {
/**
* Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
* true.
*
* @param forceSend Send the windows for accessibility even if they haven't
@@ -996,7 +996,7 @@ public class AccessibilityWindowManager {
final int windowId = findWindowIdLocked(userId, window.token);
// With the flag enabled, createWindowInfoListLocked() already removes invalid windows.
- if (!Flags.computeWindowChangesOnA11y()) {
+ if (!Flags.computeWindowChangesOnA11yV2()) {
if (windowId < 0) {
return null;
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cfeb5f4e2469..5e566aa98215 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -23,6 +23,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.res.Resources.ID_NULL;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+import static com.android.server.appwidget.AppWidgetXmlUtil.deserializeWidgetSizesStr;
+import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.Manifest;
@@ -164,7 +166,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongSupplier;
-import java.util.stream.Collectors;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
OnCrossProfileWidgetProvidersChangeListener {
@@ -179,7 +180,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private static final String STATE_FILENAME = "appwidgets.xml";
private static final String KEY_SIZES = "sizes";
- private static final String SIZE_SEPARATOR = ",";
private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000; // 30 minutes
@@ -2718,9 +2718,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
List<SizeF> sizes = widget.options.getParcelableArrayList(
AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF.class);
if (sizes != null) {
- String sizeStr = sizes.stream().map(SizeF::toString)
- .collect(Collectors.joining(SIZE_SEPARATOR));
- out.attribute(null, KEY_SIZES, sizeStr);
+ out.attribute(null, KEY_SIZES, serializeWidgetSizes(sizes));
}
if (saveRestoreCompleted) {
boolean restoreCompleted = widget.options.getBoolean(
@@ -2754,15 +2752,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight);
}
String sizesStr = parser.getAttributeValue(null, KEY_SIZES);
- if (sizesStr != null) {
- try {
- ArrayList<SizeF> sizes = Arrays.stream(sizesStr.split(SIZE_SEPARATOR))
- .map(SizeF::parseSizeF)
- .collect(Collectors.toCollection(ArrayList::new));
- options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Error parsing widget sizes", e);
- }
+ ArrayList<SizeF> sizes = deserializeWidgetSizesStr(sizesStr);
+ if (sizes != null) {
+ options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);
}
int category = parser.getAttributeIntHex(null, "host_category",
AppWidgetProviderInfo.WIDGET_CATEGORY_UNKNOWN);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 69b738a2704c..d781cd8d58d8 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -22,12 +22,18 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.os.Build;
import android.text.TextUtils;
+import android.util.SizeF;
+import android.util.Slog;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
/**
* @hide
@@ -59,6 +65,7 @@ public class AppWidgetXmlUtil {
private static final String ATTR_DESCRIPTION_RES = "description_res";
private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance";
private static final String ATTR_OS_FINGERPRINT = "os_fingerprint";
+ private static final String SIZE_SEPARATOR = ",";
/**
* @hide
@@ -137,4 +144,25 @@ public class AppWidgetXmlUtil {
ATTR_PROVIDER_INHERITANCE, false);
return info;
}
+
+ @NonNull
+ static String serializeWidgetSizes(@NonNull List<SizeF> sizes) {
+ return sizes.stream().map(SizeF::toString)
+ .collect(Collectors.joining(SIZE_SEPARATOR));
+ }
+
+ @Nullable
+ static ArrayList<SizeF> deserializeWidgetSizesStr(@Nullable String sizesStr) {
+ if (sizesStr == null || sizesStr.isEmpty()) {
+ return null;
+ }
+ try {
+ return Arrays.stream(sizesStr.split(SIZE_SEPARATOR))
+ .map(SizeF::parseSizeF)
+ .collect(Collectors.toCollection(ArrayList::new));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Error parsing widget sizes", e);
+ return null;
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
new file mode 100644
index 000000000000..0a4148535451
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.power.Mode;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.PerUser;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.utils.PackageUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages communication with companion applications via
+ * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
+ * the services, maintaining the connection (the binding), and invoking callback methods such as
+ * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
+ * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
+ *
+ * <p>
+ * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
+ * utilized by {@link CompanionDeviceManagerService}):
+ * <ul>
+ * <li> {@link #bindCompanionApplication(int, String, boolean)}
+ * <li> {@link #unbindCompanionApplication(int, String)}
+ * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
+ * <li> {@link #isCompanionApplicationBound(int, String)}
+ * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
+ * </ul>
+ *
+ * @see CompanionDeviceService
+ * @see android.companion.ICompanionDeviceService
+ * @see CompanionDeviceServiceConnector
+ */
+@SuppressLint("LongLogTag")
+public class CompanionApplicationController {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CDM_CompanionApplicationController";
+
+ private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
+
+ private final @NonNull Context mContext;
+ private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
+ private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
+
+ private final PowerManagerInternal mPowerManagerInternal;
+
+ @GuardedBy("mBoundCompanionApplications")
+ private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
+ mBoundCompanionApplications;
+ @GuardedBy("mScheduledForRebindingCompanionApplications")
+ private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
+
+ CompanionApplicationController(Context context, AssociationStore associationStore,
+ ObservableUuidStore observableUuidStore,
+ CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
+ PowerManagerInternal powerManagerInternal) {
+ mContext = context;
+ mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
+ mDevicePresenceMonitor = companionDevicePresenceMonitor;
+ mPowerManagerInternal = powerManagerInternal;
+ mCompanionServicesRegister = new CompanionServicesRegister();
+ mBoundCompanionApplications = new AndroidPackageMap<>();
+ mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
+ }
+
+ void onPackagesChanged(@UserIdInt int userId) {
+ mCompanionServicesRegister.invalidate(userId);
+ }
+
+ /**
+ * CDM binds to the companion app.
+ */
+ public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
+ boolean isSelfManaged) {
+ if (DEBUG) {
+ Log.i(TAG, "bind() u" + userId + "/" + packageName
+ + " isSelfManaged=" + isSelfManaged);
+ }
+
+ final List<ComponentName> companionServices =
+ mCompanionServicesRegister.forPackage(userId, packageName);
+ if (companionServices.isEmpty()) {
+ Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
+ + "eligible CompanionDeviceService not found.\n"
+ + "A CompanionDeviceService should declare an intent-filter for "
+ + "\"android.companion.CompanionDeviceService\" action and require "
+ + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
+ return;
+ }
+
+ final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>();
+ synchronized (mBoundCompanionApplications) {
+ if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
+ if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
+ return;
+ }
+
+ for (int i = 0; i < companionServices.size(); i++) {
+ boolean isPrimary = i == 0;
+ serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId,
+ companionServices.get(i), isSelfManaged, isPrimary));
+ }
+
+ mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
+ }
+
+ // Set listeners for both Primary and Secondary connectors.
+ for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.setListener(this::onBinderDied);
+ }
+
+ // Now "bind" all the connectors: the primary one and the rest of them.
+ for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.connect();
+ }
+ }
+
+ /**
+ * CDM unbinds the companion app.
+ */
+ public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
+ if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
+
+ final List<CompanionDeviceServiceConnector> serviceConnectors;
+
+ synchronized (mBoundCompanionApplications) {
+ serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
+ }
+
+ if (serviceConnectors == null) {
+ if (DEBUG) {
+ Log.e(TAG, "unbindCompanionApplication(): "
+ + "u" + userId + "/" + packageName + " is NOT bound");
+ Log.d(TAG, "Stacktrace", new Throwable());
+ }
+ return;
+ }
+
+ for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.postUnbind();
+ }
+ }
+
+ /**
+ * @return whether the companion application is bound now.
+ */
+ public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mBoundCompanionApplications) {
+ return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
+ }
+ }
+
+ private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
+ CompanionDeviceServiceConnector serviceConnector) {
+ Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
+
+ if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
+ if (DEBUG) {
+ Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
+ + serviceConnector.getComponentName());
+ }
+ return;
+ }
+
+ if (serviceConnector.isPrimary()) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.setValueForPackage(
+ userId, packageName, true);
+ }
+ }
+
+ // Rebinding in 10 seconds.
+ Handler.getMain().postDelayed(() ->
+ onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector),
+ REBIND_TIMEOUT);
+ }
+
+ private boolean isRebindingCompanionApplicationScheduled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ return mScheduledForRebindingCompanionApplications.containsValueForPackage(
+ userId, packageName);
+ }
+ }
+
+ private void onRebindingCompanionApplicationTimeout(
+ @UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionDeviceServiceConnector serviceConnector) {
+ // Re-mark the application is bound.
+ if (serviceConnector.isPrimary()) {
+ synchronized (mBoundCompanionApplications) {
+ if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
+ List<CompanionDeviceServiceConnector> serviceConnectors =
+ Collections.singletonList(serviceConnector);
+ mBoundCompanionApplications.setValueForPackage(userId, packageName,
+ serviceConnectors);
+ }
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
+ }
+ }
+
+ serviceConnector.connect();
+ }
+
+ /**
+ * Notify the app that the device appeared.
+ *
+ * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+ */
+ @Deprecated
+ public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId
+ + "/" + packageName);
+
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): "
+ + "u" + userId + "/" + packageName + " is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=["
+ + packageName + "] associationId=[" + association.getId() + "]");
+
+ primaryServiceConnector.postOnDeviceAppeared(association);
+ }
+
+ /**
+ * Notify the app that the device disappeared.
+ *
+ * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+ */
+ @Deprecated
+ public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId
+ + "/" + packageName);
+
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): "
+ + "u" + userId + "/" + packageName + " is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=["
+ + packageName + "] associationId=[" + association.getId() + "]");
+
+ primaryServiceConnector.postOnDeviceDisappeared(association);
+ }
+
+ /**
+ * Notify the app that the device appeared.
+ */
+ public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) {
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ final DevicePresenceEvent devicePresenceEvent =
+ new DevicePresenceEvent(association.getId(), event, null);
+
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
+ + "u" + userId + "/" + packageName
+ + " event=[ " + event + " ] is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+ + packageName + "] associationId=[" + association.getId()
+ + "] event=[" + event + "]");
+
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
+ }
+
+ /**
+ * Notify the app that the device disappeared.
+ */
+ public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) {
+ final int userId = uuid.getUserId();
+ final ParcelUuid parcelUuid = uuid.getUuid();
+ final String packageName = uuid.getPackageName();
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ final DevicePresenceEvent devicePresenceEvent =
+ new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);
+
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
+ + "u" + userId + "/" + packageName
+ + " event=[ " + event + " ] is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+ + packageName + "]" + "event= [" + event + "]");
+
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
+ }
+
+ void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Application Controller: \n");
+
+ synchronized (mBoundCompanionApplications) {
+ out.append(" Bound Companion Applications: ");
+ if (mBoundCompanionApplications.size() == 0) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ mBoundCompanionApplications.dump(out);
+ }
+ }
+
+ out.append(" Companion Applications Scheduled For Rebinding: ");
+ if (mScheduledForRebindingCompanionApplications.size() == 0) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ mScheduledForRebindingCompanionApplications.dump(out);
+ }
+ }
+
+ /**
+ * Rebinding for Self-Managed secondary services OR Non-Self-Managed services.
+ */
+ private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionDeviceServiceConnector serviceConnector) {
+
+ boolean isPrimary = serviceConnector.isPrimary();
+ Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
+
+ // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+ if (isPrimary) {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+
+ for (AssociationInfo association : associations) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ break;
+ }
+ }
+
+ synchronized (mBoundCompanionApplications) {
+ mBoundCompanionApplications.removePackage(userId, packageName);
+ }
+ }
+
+ // Second: schedule rebinding if needed.
+ final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
+
+ if (shouldScheduleRebind) {
+ scheduleRebinding(userId, packageName, serviceConnector);
+ }
+ }
+
+ private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final List<CompanionDeviceServiceConnector> connectors;
+ synchronized (mBoundCompanionApplications) {
+ connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
+ }
+ return connectors != null ? connectors.get(0) : null;
+ }
+
+ private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
+ // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
+ // app is uninstalled.
+ boolean stillAssociated = false;
+ // Make sure to clean up the state for all the associations
+ // that associate with this package.
+ boolean shouldScheduleRebind = false;
+ boolean shouldScheduleRebindForUuid = false;
+ final List<ObservableUuid> uuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (AssociationInfo ai :
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
+ final int associationId = ai.getId();
+ stillAssociated = true;
+ if (ai.isSelfManaged()) {
+ // Do not rebind if primary one is died for selfManaged application.
+ if (isPrimary
+ && mDevicePresenceMonitor.isDevicePresent(associationId)) {
+ mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
+ shouldScheduleRebind = false;
+ }
+ // Do not rebind if both primary and secondary services are died for
+ // selfManaged application.
+ shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
+ } else if (ai.isNotifyOnDeviceNearby()) {
+ // Always rebind for non-selfManaged devices.
+ shouldScheduleRebind = true;
+ }
+ }
+
+ for (ObservableUuid uuid : uuids) {
+ if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+ shouldScheduleRebindForUuid = true;
+ break;
+ }
+ }
+
+ return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
+ }
+
+ private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
+ @Override
+ public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+ @UserIdInt int userId) {
+ return super.forUser(userId);
+ }
+
+ synchronized @NonNull List<ComponentName> forPackage(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return forUser(userId).getOrDefault(packageName, Collections.emptyList());
+ }
+
+ synchronized void invalidate(@UserIdInt int userId) {
+ remove(userId);
+ }
+
+ @Override
+ protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+ return PackageUtils.getCompanionServicesForUser(mContext, userId);
+ }
+ }
+
+ /**
+ * Associates an Android package (defined by userId + packageName) with a value of type T.
+ */
+ private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> {
+
+ void setValueForPackage(
+ @UserIdInt int userId, @NonNull String packageName, @NonNull T value) {
+ Map<String, T> forUser = get(userId);
+ if (forUser == null) {
+ forUser = /* Map<String, T> */ new HashMap();
+ put(userId, forUser);
+ }
+
+ forUser.put(packageName, value);
+ }
+
+ boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final Map<String, ?> forUser = get(userId);
+ return forUser != null && forUser.containsKey(packageName);
+ }
+
+ T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final Map<String, T> forUser = get(userId);
+ return forUser != null ? forUser.get(packageName) : null;
+ }
+
+ T removePackage(@UserIdInt int userId, @NonNull String packageName) {
+ final Map<String, T> forUser = get(userId);
+ if (forUser == null) return null;
+ return forUser.remove(packageName);
+ }
+
+ void dump() {
+ if (size() == 0) {
+ Log.d(TAG, "<empty>");
+ return;
+ }
+
+ for (int i = 0; i < size(); i++) {
+ final int userId = keyAt(i);
+ final Map<String, T> forUser = get(userId);
+ if (forUser.isEmpty()) {
+ Log.d(TAG, "u" + userId + ": <empty>");
+ }
+
+ for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
+ final String packageName = packageValue.getKey();
+ final T value = packageValue.getValue();
+ Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value);
+ }
+ }
+ }
+
+ private void dump(@NonNull PrintWriter out) {
+ for (int i = 0; i < size(); i++) {
+ final int userId = keyAt(i);
+ final Map<String, T> forUser = get(userId);
+ if (forUser.isEmpty()) {
+ out.append(" u").append(String.valueOf(userId)).append(": <empty>\n");
+ }
+
+ for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
+ final String packageName = packageValue.getKey();
+ final T value = packageValue.getValue();
+ out.append(" u").append(String.valueOf(userId)).append("\\")
+ .append(packageName).append(" -> ")
+ .append(value.toString()).append('\n');
+ }
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index edf9fd13d51f..712162b2d3b5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,10 +20,15 @@ package com.android.server.companion;
import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -37,10 +42,13 @@ import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.EnforcePermission;
@@ -56,6 +64,7 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -70,6 +79,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.hardware.power.Mode;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -81,6 +91,7 @@ import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -107,8 +118,7 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -121,7 +131,10 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
@SuppressLint("LongLogTag")
@@ -133,6 +146,10 @@ public class CompanionDeviceManagerService extends SystemService {
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
+ private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
+ "debug.cdm.cdmservice.removal_time_window";
+
+ private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
private static final int MAX_CN_LENGTH = 500;
private final ActivityTaskManagerInternal mAtmInternal;
@@ -148,8 +165,8 @@ public class CompanionDeviceManagerService extends SystemService {
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
- private final DevicePresenceProcessor mDevicePresenceProcessor;
- private final CompanionAppBinder mCompanionAppBinder;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final CompanionApplicationController mCompanionAppController;
private final CompanionTransportManager mTransportManager;
private final DisassociationProcessor mDisassociationProcessor;
private final CrossDeviceSyncController mCrossDeviceSyncController;
@@ -168,7 +185,7 @@ public class CompanionDeviceManagerService extends SystemService {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
- mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
+ mAssociationStore = new AssociationStore(userManager, associationDiskStore);
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mObservableUuidStore = new ObservableUuidStore();
@@ -179,17 +196,18 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
- mCompanionAppBinder = new CompanionAppBinder(context);
+ mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
+ mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
- mDevicePresenceProcessor = new DevicePresenceProcessor(context,
- mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
+ mCompanionAppController = new CompanionApplicationController(
+ context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
- mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
- mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
+ mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
+ mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -224,7 +242,7 @@ public class CompanionDeviceManagerService extends SystemService {
// delays (even in case of the Main Thread). It may be fine overall, but would require
// updating the tests (adding a delay there).
mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
- mDevicePresenceProcessor.init(context);
+ mDevicePresenceMonitor.init(context);
} else if (phase == PHASE_BOOT_COMPLETED) {
// Run the Inactive Association Removal job service daily.
InactiveAssociationsRemovalService.schedule(getContext());
@@ -253,7 +271,7 @@ public class CompanionDeviceManagerService extends SystemService {
// Notify and bind the app after the phone is unlocked.
final int userId = user.getUserIdentifier();
final Set<BluetoothDevice> blueToothDevices =
- mDevicePresenceProcessor.getPendingConnectedDevices().get(userId);
+ mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
@@ -269,14 +287,14 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore.getActiveAssociationsByAddress(
bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
- mDevicePresenceProcessor.onBluetoothCompanionDeviceConnected(ai.getId());
+ mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
}
for (ObservableUuid observableUuid : observableUuids) {
if (deviceUuids.contains(observableUuid.getUuid())) {
Slog.i(TAG, "onUserUnlocked, UUID( "
+ observableUuid.getUuid() + " ) is connected");
- mDevicePresenceProcessor.onDevicePresenceEventByUuid(
+ mDevicePresenceMonitor.onDevicePresenceEventByUuid(
observableUuid, EVENT_BT_CONNECTED);
}
}
@@ -284,6 +302,181 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
+ @NonNull
+ AssociationInfo getAssociationWithCallerChecks(
+ @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
+ userId, packageName, macAddress);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
+ }
+
+ @NonNull
+ AssociationInfo getAssociationWithCallerChecks(int associationId) {
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
+ }
+
+ private void onDeviceAppearedInternal(int associationId) {
+ if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId);
+
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (DEBUG) Log.d(TAG, " association=" + association);
+
+ if (!association.shouldBindWhenPresent()) return;
+
+ bindApplicationIfNeeded(association);
+
+ mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association);
+ }
+
+ private void onDeviceDisappearedInternal(int associationId) {
+ if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId);
+
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (DEBUG) Log.d(TAG, " association=" + association);
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+ return;
+ }
+
+ if (association.shouldBindWhenPresent()) {
+ mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association);
+ }
+ }
+
+ private void onDevicePresenceEventInternal(int associationId, int event) {
+ Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event);
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
+ if (!association.shouldBindWhenPresent()) return;
+
+ bindApplicationIfNeeded(association);
+
+ mCompanionAppController.notifyCompanionDevicePresenceEvent(
+ association, event);
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+ return;
+ }
+ if (association.shouldBindWhenPresent()) {
+ mCompanionAppController.notifyCompanionDevicePresenceEvent(
+ association, event);
+ }
+ // Check if there are other devices associated to the app that are present.
+ if (shouldBindPackage(userId, packageName)) return;
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + "is not supported");
+ break;
+ }
+ }
+
+ private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) {
+ Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid()
+ + "for package=" + uuid.getPackageName() + " event=" + event);
+ final String packageName = uuid.getPackageName();
+ final int userId = uuid.getUserId();
+
+ switch (event) {
+ case EVENT_BT_CONNECTED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppController.bindCompanionApplication(
+ userId, packageName, /*bindImportant*/ false);
+
+ } else if (DEBUG) {
+ Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+ }
+
+ mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
+
+ break;
+ case EVENT_BT_DISCONNECTED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+ return;
+ }
+
+ mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
+ // Check if there are other devices associated to the app or the UUID to be
+ // observed are present.
+ if (shouldBindPackage(userId, packageName)) return;
+
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + "is not supported");
+ break;
+ }
+ }
+
+ private void bindApplicationIfNeeded(AssociationInfo association) {
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ // Set bindImportant to true when the association is self-managed to avoid the target
+ // service being killed.
+ final boolean bindImportant = association.isSelfManaged();
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppController.bindCompanionApplication(
+ userId, packageName, bindImportant);
+ } else if (DEBUG) {
+ Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+ }
+ }
+
+ /**
+ * @return whether the package should be bound (i.e. at least one of the devices associated with
+ * the package is currently present OR the UUID to be observed by this package is
+ * currently present).
+ */
+ private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final List<AssociationInfo> packageAssociations =
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (AssociationInfo association : packageAssociations) {
+ if (!association.shouldBindWhenPresent()) continue;
+ if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
+ }
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private void onPackageRemoveOrDataClearedInternal(
@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) {
@@ -309,7 +502,7 @@ public class CompanionDeviceManagerService extends SystemService {
mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
}
- mCompanionAppBinder.onPackagesChanged(userId);
+ mCompanionAppController.onPackagesChanged(userId);
}
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -322,15 +515,34 @@ public class CompanionDeviceManagerService extends SystemService {
association.getPackageName());
}
- mCompanionAppBinder.onPackagesChanged(userId);
+ mCompanionAppController.onPackagesChanged(userId);
}
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
}
+ // Revoke associations if the selfManaged companion device does not connect for 3 months.
void removeInactiveSelfManagedAssociations() {
- mDisassociationProcessor.removeIdleSelfManagedAssociations();
+ final long currentTime = System.currentTimeMillis();
+ long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
+ if (removalWindow <= 0) {
+ // 0 or negative values indicate that the sysprop was never set or should be ignored.
+ removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
+ }
+
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association id=" + id);
+ mDisassociationProcessor.disassociate(id);
+ }
}
public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@@ -467,15 +679,24 @@ public class CompanionDeviceManagerService extends SystemService {
@Deprecated
@Override
public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
+ Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
+ + ", macAddress=" + deviceMacAddress);
+
requireNonNull(deviceMacAddress);
requireNonNull(packageName);
- mDisassociationProcessor.disassociate(userId, packageName, deviceMacAddress);
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
+ mDisassociationProcessor.disassociate(association.getId());
}
@Override
public void disassociate(int associationId) {
- mDisassociationProcessor.disassociate(associationId);
+ Slog.i(TAG, "disassociate() associationId=" + associationId);
+
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(associationId);
+ mDisassociationProcessor.disassociate(association.getId());
}
@Override
@@ -537,25 +758,21 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void legacyStartObservingDevicePresence(String deviceAddress, String callingPackage,
- int userId) throws RemoteException {
- legacyStartObservingDevicePresence_enforcePermission();
-
- mDevicePresenceProcessor.startObservingDevicePresence(userId, callingPackage,
- deviceAddress);
+ public void registerDevicePresenceListenerService(String deviceAddress,
+ String callingPackage, int userId) throws RemoteException {
+ registerDevicePresenceListenerService_enforcePermission();
+ // TODO: take the userId into account.
+ registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
}
@Override
- @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void legacyStopObservingDevicePresence(String deviceAddress, String callingPackage,
- int userId) throws RemoteException {
- legacyStopObservingDevicePresence_enforcePermission();
-
- mDevicePresenceProcessor.stopObservingDevicePresence(userId, callingPackage,
- deviceAddress);
+ public void unregisterDevicePresenceListenerService(String deviceAddress,
+ String callingPackage, int userId) throws RemoteException {
+ unregisterDevicePresenceListenerService_enforcePermission();
+ // TODO: take the userId into account.
+ registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
}
@Override
@@ -563,8 +780,7 @@ public class CompanionDeviceManagerService extends SystemService {
public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
startObservingDevicePresence_enforcePermission();
-
- mDevicePresenceProcessor.startObservingDevicePresence(request, packageName, userId);
+ registerDevicePresenceListener(request, packageName, userId, /* active */ true);
}
@Override
@@ -572,8 +788,80 @@ public class CompanionDeviceManagerService extends SystemService {
public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
stopObservingDevicePresence_enforcePermission();
+ registerDevicePresenceListener(request, packageName, userId, /* active */ false);
+ }
+
+ private void registerDevicePresenceListener(ObservingDevicePresenceRequest request,
+ String packageName, int userId, boolean active) {
+ enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
+ enforceCallerIsSystemOr(userId, packageName);
+
+ final int associationId = request.getAssociationId();
+ final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
+ associationId);
+ final ParcelUuid uuid = request.getUuid();
+
+ if (uuid != null) {
+ enforceCallerCanObservingDevicePresenceByUuid(getContext());
+ if (active) {
+ startObservingDevicePresenceByUuid(uuid, packageName, userId);
+ } else {
+ stopObservingDevicePresenceByUuid(uuid, packageName, userId);
+ }
+ } else if (associationInfo == null) {
+ throw new IllegalArgumentException("App " + packageName
+ + " is not associated with device " + request.getAssociationId()
+ + " for user " + userId);
+ } else {
+ processDevicePresenceListener(
+ associationInfo, userId, packageName, active);
+ }
+ }
+
+ private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+ int userId) {
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (ObservableUuid observableUuid : observableUuids) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName
+ + "has been already scheduled for observing");
+ return;
+ }
+ }
+
+ final ObservableUuid observableUuid = new ObservableUuid(userId, uuid,
+ packageName, System.currentTimeMillis());
+
+ mObservableUuidStore.writeObservableUuid(userId, observableUuid);
+ }
+
+ private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+ int userId) {
+ final List<ObservableUuid> uuidsTobeObserved =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+ boolean isScheduledObserving = false;
+
+ for (ObservableUuid observableUuid : uuidsTobeObserved) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ isScheduledObserving = true;
+ break;
+ }
+ }
- mDevicePresenceProcessor.stopObservingDevicePresence(request, packageName, userId);
+ if (!isScheduledObserving) {
+ Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName
+ + "has NOT been scheduled for observing yet");
+ return;
+ }
+
+ mObservableUuidStore.removeObservableUuid(userId, uuid, packageName);
+ mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid);
+
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
}
@Override
@@ -586,7 +874,8 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public boolean isPermissionTransferUserConsented(String packageName, int userId,
int associationId) {
- return mSystemDataTransferProcessor.isPermissionTransferUserConsented(associationId);
+ return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName,
+ userId, associationId);
}
@Override
@@ -602,7 +891,8 @@ public class CompanionDeviceManagerService extends SystemService {
ParcelFileDescriptor fd) {
attachSystemDataTransport_enforcePermission();
- mTransportManager.attachSystemDataTransport(associationId, fd);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
}
@Override
@@ -610,61 +900,161 @@ public class CompanionDeviceManagerService extends SystemService {
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
detachSystemDataTransport_enforcePermission();
- mTransportManager.detachSystemDataTransport(associationId);
- }
-
- @Override
- @EnforcePermission(MANAGE_COMPANION_DEVICES)
- public void enableSecureTransport(boolean enabled) {
- enableSecureTransport_enforcePermission();
-
- mTransportManager.enableSecureTransport(enabled);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
}
@Override
public void enableSystemDataSync(int associationId, int flags) {
+ getAssociationWithCallerChecks(associationId);
mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags);
}
@Override
public void disableSystemDataSync(int associationId, int flags) {
+ getAssociationWithCallerChecks(associationId);
mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags);
}
@Override
public void enablePermissionsSync(int associationId) {
+ getAssociationWithCallerChecks(associationId);
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
}
@Override
public void disablePermissionsSync(int associationId) {
+ getAssociationWithCallerChecks(associationId);
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
}
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ // TODO: temporary fix, will remove soon
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (association == null) {
+ return null;
+ }
+ getAssociationWithCallerChecks(associationId);
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
}
@Override
- @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
- public void notifySelfManagedDeviceAppeared(int associationId) {
- notifySelfManagedDeviceAppeared_enforcePermission();
+ @EnforcePermission(MANAGE_COMPANION_DEVICES)
+ public void enableSecureTransport(boolean enabled) {
+ enableSecureTransport_enforcePermission();
+ mTransportManager.enableSecureTransport(enabled);
+ }
- mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, true);
+ @Override
+ public void notifyDeviceAppeared(int associationId) {
+ if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
+
+ AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association with ID " + associationId
+ + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+ + " self-managed associations.");
+ }
+ // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+ // timestamp.
+ association = (new AssociationInfo.Builder(association))
+ .setLastTimeConnected(System.currentTimeMillis())
+ .build();
+ mAssociationStore.updateAssociation(association);
+
+ mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
+ }
}
@Override
- @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
- public void notifySelfManagedDeviceDisappeared(int associationId) {
- notifySelfManagedDeviceDisappeared_enforcePermission();
+ public void notifyDeviceDisappeared(int associationId) {
+ if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
+
+ final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association with ID " + associationId
+ + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+ + " self-managed associations.");
+ }
+
+ mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
- mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, false);
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ }
}
@Override
public boolean isCompanionApplicationBound(String packageName, int userId) {
- return mCompanionAppBinder.isCompanionApplicationBound(userId, packageName);
+ return mCompanionAppController.isCompanionApplicationBound(userId, packageName);
+ }
+
+ private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
+ boolean active) throws RemoteException {
+ if (DEBUG) {
+ Log.i(TAG, "registerDevicePresenceListenerActive()"
+ + " active=" + active
+ + " deviceAddress=" + deviceAddress);
+ }
+ final int userId = getCallingUserId();
+ enforceCallerIsSystemOr(userId, packageName);
+
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
+ userId, packageName, deviceAddress);
+
+ if (association == null) {
+ throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
+ + " is not associated with device " + deviceAddress
+ + " for user " + userId));
+ }
+
+ processDevicePresenceListener(association, userId, packageName, active);
+ }
+
+ private void processDevicePresenceListener(AssociationInfo association,
+ int userId, String packageName, boolean active) {
+ // If already at specified state, then no-op.
+ if (active == association.isNotifyOnDeviceNearby()) {
+ if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state.");
+ return;
+ }
+
+ // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+ // flag.
+ association = (new AssociationInfo.Builder(association))
+ .setNotifyOnDeviceNearby(active)
+ .build();
+ // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will
+ // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when
+ // an application sets/unsets the mNotifyOnDeviceNearby flag.
+ mAssociationStore.updateAssociation(association);
+
+ int associationId = association.getId();
+ // If device is already present, then trigger callback.
+ if (active && mDevicePresenceMonitor.isDevicePresent(associationId)) {
+ Slog.i(TAG, "Device is already present. Triggering callback.");
+ if (mDevicePresenceMonitor.isBlePresent(associationId)
+ || mDevicePresenceMonitor.isSimulatePresent(associationId)) {
+ onDeviceAppearedInternal(associationId);
+ onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
+ } else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
+ onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
+ }
+ }
+
+ // If last listener is unregistered, then unbind application.
+ if (!active && !shouldBindPackage(userId, packageName)) {
+ if (DEBUG) Log.d(TAG, "Last listener unregistered. Unbinding application.");
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
}
@Override
@@ -680,8 +1070,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
- mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null);
+ createNewAssociation(userId, packageName, macAddressObj, null, null, false);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -710,7 +1099,9 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void setAssociationTag(int associationId, String tag) {
- mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
+ AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ mAssociationStore.updateAssociation(association);
}
@Override
@@ -733,7 +1124,7 @@ public class CompanionDeviceManagerService extends SystemService {
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
- mAssociationStore, mDevicePresenceProcessor, mTransportManager,
+ mAssociationStore, mDevicePresenceMonitor, mTransportManager,
mSystemDataTransferProcessor, mAssociationRequestsProcessor,
mBackupRestoreProcessor, mDisassociationProcessor)
.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
@@ -748,13 +1139,21 @@ public class CompanionDeviceManagerService extends SystemService {
}
mAssociationStore.dump(out);
- mDevicePresenceProcessor.dump(out);
- mCompanionAppBinder.dump(out);
+ mDevicePresenceMonitor.dump(out);
+ mCompanionAppController.dump(out);
mTransportManager.dump(out);
mSystemDataTransferRequestStore.dump(out);
}
}
+ void createNewAssociation(@UserIdInt int userId, @NonNull String packageName,
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String deviceProfile, boolean isSelfManaged) {
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
+ displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged,
+ /* callback */ null, /* resultReceiver */ null);
+ }
+
/**
* Update special access for the association's package
*/
@@ -770,6 +1169,8 @@ public class CompanionDeviceManagerService extends SystemService {
return;
}
+ Slog.i(TAG, "Updating special access for package=[" + packageInfo.packageName + "]...");
+
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.RUN_IN_BACKGROUND,
android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -879,6 +1280,29 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
+ private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
+ new CompanionDevicePresenceMonitor.Callback() {
+ @Override
+ public void onDeviceAppeared(int associationId) {
+ onDeviceAppearedInternal(associationId);
+ }
+
+ @Override
+ public void onDeviceDisappeared(int associationId) {
+ onDeviceDisappearedInternal(associationId);
+ }
+
+ @Override
+ public void onDevicePresenceEvent(int associationId, int event) {
+ onDevicePresenceEventInternal(associationId, event);
+ }
+
+ @Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+ onDevicePresenceEventByUuidInternal(uuid, event);
+ }
+ };
+
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
@@ -891,7 +1315,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void onPackageModified(@NonNull String packageName) {
+ public void onPackageModified(String packageName) {
onPackageModifiedInternal(getChangingUserId(), packageName);
}
@@ -901,15 +1325,25 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
+ private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
+ final Map<String, Set<Integer>> copy = new HashMap<>();
+
+ for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) {
+ final Set<Integer> valueCopy = new HashSet<>(entry.getValue());
+ copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy));
+ }
+
+ return Collections.unmodifiableMap(copy);
+ }
+
private static <T> boolean containsEither(T[] array, T a, T b) {
return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
}
private class LocalService implements CompanionDeviceManagerServiceInternal {
-
@Override
public void removeInactiveSelfManagedAssociations() {
- mDisassociationProcessor.removeIdleSelfManagedAssociations();
+ CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations();
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index 9d1250d361c4..cdf832f8c788 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -27,9 +27,8 @@ import java.util.Collection;
* Companion Device Manager Local System Service Interface.
*/
public interface CompanionDeviceManagerServiceInternal {
-
/**
- * Remove idle self-managed associations.
+ * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations
*/
void removeInactiveSelfManagedAssociations();
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index c01c3195e04d..5abdb42b34fc 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
@@ -33,42 +33,36 @@ import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.infra.ServiceConnector;
import com.android.server.ServiceThread;
-import com.android.server.companion.CompanionDeviceManagerService;
/**
* Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
* application process.
*/
@SuppressLint("LongLogTag")
-public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
-
- /** Listener for changes to the state of the {@link CompanionServiceConnector} */
- public interface Listener {
- /**
- * Called when service binding is died.
- */
- void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionServiceConnector serviceConnector);
- }
-
+class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
private static final String TAG = "CDM_CompanionServiceConnector";
+ private static final boolean DEBUG = false;
/* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
private static final long UNBIND_POST_DELAY_MS = 5_000;
- @UserIdInt
- private final int mUserId;
- @NonNull
- private final ComponentName mComponentName;
- private final boolean mIsPrimary;
+
+ /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
+ interface Listener {
+ void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionDeviceServiceConnector serviceConnector);
+ }
+
+ private final @UserIdInt int mUserId;
+ private final @NonNull ComponentName mComponentName;
// IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only
// installs a listener to the primary ServiceConnector), hence we should always null-check the
// reference before calling on it.
- @Nullable
- private Listener mListener;
+ private @Nullable Listener mListener;
+ private boolean mIsPrimary;
/**
* Create a CompanionDeviceServiceConnector instance.
@@ -85,16 +79,16 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
* IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the
* service importance level should be higher than 125.
*/
- static CompanionServiceConnector newInstance(@NonNull Context context,
+ static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
@UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged,
boolean isPrimary) {
final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
: BIND_ALMOST_PERCEPTIBLE;
- return new CompanionServiceConnector(
+ return new CompanionDeviceServiceConnector(
context, userId, componentName, bindingFlags, isPrimary);
}
- private CompanionServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
@NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
super(context, buildIntent(componentName), bindingFlags, userId, null);
mUserId = userId;
@@ -139,7 +133,6 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
return mIsPrimary;
}
- @NonNull
ComponentName getComponentName() {
return mComponentName;
}
@@ -147,15 +140,17 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
@Override
protected void onServiceConnectionStatusChanged(
@NonNull ICompanionDeviceService service, boolean isConnected) {
- Slog.d(TAG, "onServiceConnectionStatusChanged() " + mComponentName.toShortString()
- + " connected=" + isConnected);
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
+ }
}
@Override
public void binderDied() {
super.binderDied();
- Slog.d(TAG, "binderDied() " + mComponentName.toShortString());
+ if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString());
// Handle primary process being killed
if (mListener != null) {
@@ -177,8 +172,7 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
* within system_server and thus tends to get heavily congested)
*/
@Override
- @NonNull
- protected Handler getJobHandler() {
+ protected @NonNull Handler getJobHandler() {
return getServiceThread().getThreadHandler();
}
@@ -188,14 +182,12 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
return -1;
}
- @NonNull
- private static Intent buildIntent(@NonNull ComponentName componentName) {
+ private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
.setComponent(componentName);
}
- @NonNull
- private static ServiceThread getServiceThread() {
+ private static @NonNull ServiceThread getServiceThread() {
if (sServiceThread == null) {
synchronized (CompanionDeviceManagerService.class) {
if (sServiceThread == null) {
@@ -214,6 +206,5 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
* <p>
* Do NOT reference directly, use {@link #getServiceThread()} method instead.
*/
- @Nullable
- private static volatile ServiceThread sServiceThread;
+ private static volatile @Nullable ServiceThread sServiceThread;
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a78938400a1e..a7a73cb6bddb 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@ package com.android.server.companion;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
+
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -36,7 +38,7 @@ import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -49,7 +51,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
private final CompanionDeviceManagerService mService;
private final DisassociationProcessor mDisassociationProcessor;
private final AssociationStore mAssociationStore;
- private final DevicePresenceProcessor mDevicePresenceProcessor;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -58,7 +60,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- DevicePresenceProcessor devicePresenceProcessor,
+ CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
@@ -66,7 +68,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
DisassociationProcessor disassociationProcessor) {
mService = service;
mAssociationStore = associationStore;
- mDevicePresenceProcessor = devicePresenceProcessor;
+ mDevicePresenceMonitor = devicePresenceMonitor;
mTransportManager = transportManager;
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
@@ -83,7 +85,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
associationId = getNextIntArgRequired();
int event = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
return 0;
}
@@ -95,7 +97,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
ObservableUuid observableUuid = new ObservableUuid(
userId, ParcelUuid.fromString(uuid), packageName,
System.currentTimeMillis());
- mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
+ mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
return 0;
}
@@ -122,9 +124,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
String address = getNextArgRequired();
String deviceProfile = getNextArg();
final MacAddress macAddress = MacAddress.fromString(address);
- mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, false,
- /* callback */ null, /* resultReceiver */ null);
+ mService.createNewAssociation(userId, packageName, macAddress,
+ /* displayName= */ deviceProfile, deviceProfile, false);
}
break;
@@ -133,13 +134,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
- mAssociationStore.getFirstAssociationByAddress(userId, packageName,
- address);
- if (association == null) {
- out.println("Association doesn't exist.");
- } else {
- mDisassociationProcessor.disassociate(association.getId());
- }
+ mService.getAssociationWithCallerChecks(userId, packageName, address);
+ mDisassociationProcessor.disassociate(association.getId());
}
break;
@@ -148,7 +144,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- mDisassociationProcessor.disassociate(association.getId());
+ if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
}
}
break;
@@ -159,12 +157,12 @@ class CompanionDeviceShellCommand extends ShellCommand {
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 0);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
break;
case "simulate-device-disappeared":
associationId = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
break;
case "get-backup-payload": {
@@ -412,9 +410,10 @@ class CompanionDeviceShellCommand extends ShellCommand {
pw.println(" Remove an existing Association.");
pw.println(" disassociate-all USER_ID");
pw.println(" Remove all Associations for a user.");
- pw.println(" refresh-cache");
+ pw.println(" clear-association-memory-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
- pw.println(" information from disk. USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
pw.println(" simulate-device-appeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has appeared.");
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a18776e67200..a02d9f912bcd 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -145,8 +145,7 @@ public class AssociationRequestsProcessor {
/**
* Handle incoming {@link AssociationRequest}s, sent via
- * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest,
- * IAssociationRequestCallback, String, int)}
+ * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
*/
public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@@ -213,8 +212,7 @@ public class AssociationRequestsProcessor {
// 2b.4. Send the PendingIntent back to the app.
try {
callback.onAssociationPending(pendingIntent);
- } catch (RemoteException ignore) {
- }
+ } catch (RemoteException ignore) { }
}
/**
@@ -254,8 +252,7 @@ public class AssociationRequestsProcessor {
// forward it back to the application via the callback.
try {
callback.onFailure(e.getMessage());
- } catch (RemoteException ignore) {
- }
+ } catch (RemoteException ignore) { }
return;
}
@@ -325,8 +322,7 @@ public class AssociationRequestsProcessor {
* Enable system data sync.
*/
public void enableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() | flags).build();
mAssociationStore.updateAssociation(updated);
@@ -336,23 +332,12 @@ public class AssociationRequestsProcessor {
* Disable system data sync.
*/
public void disableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() & (~flags)).build();
mAssociationStore.updateAssociation(updated);
}
- /**
- * Set association tag.
- */
- public void setAssociationTag(int associationId, String tag) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
- mAssociationStore.updateAssociation(association);
- }
-
private void sendCallbackAndFinish(@Nullable AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -411,14 +396,14 @@ public class AssociationRequestsProcessor {
// If the application already has a pending association request, that PendingIntent
// will be cancelled except application wants to cancel the request by the system.
return Binder.withCleanCallingIdentity(() ->
- PendingIntent.getActivityAsUser(
- mContext, /*requestCode */ packageUid, intent,
- FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
- ActivityOptions.makeBasic()
- .setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .toBundle(),
- UserHandle.CURRENT)
+ PendingIntent.getActivityAsUser(
+ mContext, /*requestCode */ packageUid, intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
+ UserHandle.CURRENT)
);
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index ae2b70852a35..edebb55233d0 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,7 +18,6 @@ package com.android.server.companion.association;
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,7 +26,6 @@ import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.IOnAssociationsChangedListener;
-import android.content.Context;
import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Binder;
@@ -59,22 +57,21 @@ import java.util.concurrent.Executors;
@SuppressLint("LongLogTag")
public class AssociationStore {
- @IntDef(prefix = {"CHANGE_TYPE_"}, value = {
+ @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
CHANGE_TYPE_ADDED,
CHANGE_TYPE_REMOVED,
CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ChangeType {
- }
+ public @interface ChangeType {}
public static final int CHANGE_TYPE_ADDED = 0;
public static final int CHANGE_TYPE_REMOVED = 1;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
- /** Listener for any changes to associations. */
+ /** Listener for any changes to associations. */
public interface OnChangeListener {
/**
* Called when there are association changes.
@@ -103,30 +100,25 @@ public class AssociationStore {
/**
* Called when an association is added.
*/
- default void onAssociationAdded(AssociationInfo association) {
- }
+ default void onAssociationAdded(AssociationInfo association) {}
/**
* Called when an association is removed.
*/
- default void onAssociationRemoved(AssociationInfo association) {
- }
+ default void onAssociationRemoved(AssociationInfo association) {}
/**
* Called when an association is updated.
*/
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
- }
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
}
private static final String TAG = "CDM_AssociationStore";
- private final Context mContext;
- private final UserManager mUserManager;
- private final AssociationDiskStore mDiskStore;
+ private final Object mLock = new Object();
+
private final ExecutorService mExecutor;
- private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mPersisted = false;
@GuardedBy("mLock")
@@ -140,9 +132,10 @@ public class AssociationStore {
private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
new RemoteCallbackList<>();
- public AssociationStore(Context context, UserManager userManager,
- AssociationDiskStore diskStore) {
- mContext = context;
+ private final UserManager mUserManager;
+ private final AssociationDiskStore mDiskStore;
+
+ public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
mUserManager = userManager;
mDiskStore = diskStore;
mExecutor = Executors.newSingleThreadExecutor();
@@ -209,7 +202,7 @@ public class AssociationStore {
synchronized (mLock) {
if (mIdToAssociationMap.containsKey(id)) {
- Slog.e(TAG, "Association id=[" + id + "] already exists.");
+ Slog.e(TAG, "Association with id=[" + id + "] already exists.");
return;
}
@@ -456,26 +449,6 @@ public class AssociationStore {
}
/**
- * Get association by id with caller checks.
- */
- @NonNull
- public AssociationInfo getAssociationWithCallerChecks(int associationId) {
- AssociationInfo association = getAssociationById(associationId);
- if (association == null) {
- throw new IllegalArgumentException(
- "getAssociationWithCallerChecks() Association id=[" + associationId
- + "] doesn't exist.");
- }
- if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
- association.getPackageName())) {
- return association;
- }
-
- throw new IllegalArgumentException(
- "The caller can't interact with the association id=[" + associationId + "].");
- }
-
- /**
* Register a local listener for association changes.
*/
public void registerLocalListener(@NonNull OnChangeListener listener) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index acf683d387a3..ec8977918c56 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -22,8 +22,6 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO
import static com.android.internal.util.CollectionUtils.any;
import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
-import static java.util.concurrent.TimeUnit.DAYS;
-
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -32,27 +30,21 @@ import android.companion.AssociationInfo;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Slog;
+import com.android.server.companion.CompanionApplicationController;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
- * This class responsible for disassociation.
+ * A class response for Association removal.
*/
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
private static final String TAG = "CDM_DisassociationProcessor";
-
- private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
- "debug.cdm.cdmservice.removal_time_window";
- private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
-
@NonNull
private final Context mContext;
@NonNull
@@ -60,11 +52,11 @@ public class DisassociationProcessor {
@NonNull
private final PackageManagerInternal mPackageManagerInternal;
@NonNull
- private final DevicePresenceProcessor mDevicePresenceMonitor;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
- private final CompanionAppBinder mCompanionAppController;
+ private final CompanionApplicationController mCompanionAppController;
@NonNull
private final CompanionTransportManager mTransportManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
@@ -74,8 +66,8 @@ public class DisassociationProcessor {
@NonNull ActivityManager activityManager,
@NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
- @NonNull DevicePresenceProcessor devicePresenceMonitor,
- @NonNull CompanionAppBinder applicationController,
+ @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
+ @NonNull CompanionApplicationController applicationController,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull CompanionTransportManager companionTransportManager) {
mContext = context;
@@ -97,7 +89,11 @@ public class DisassociationProcessor {
public void disassociate(int id) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
- final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
+ final AssociationInfo association = mAssociationStore.getAssociationById(id);
+ if (association == null) {
+ Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
+ return;
+ }
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -122,12 +118,12 @@ public class DisassociationProcessor {
return;
}
- // Detach transport if exists
- mTransportManager.detachSystemDataTransport(id);
-
// Association cleanup.
- mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
mAssociationStore.removeAssociation(association.getId());
+ mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
+
+ // Detach transport if exists
+ mTransportManager.detachSystemDataTransport(packageName, userId, id);
// If role is not in use by other associations, revoke the role.
// Do not need to remove the system role since it was pre-granted by the system.
@@ -147,28 +143,10 @@ public class DisassociationProcessor {
it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (!shouldStayBound) {
- mCompanionAppController.unbindCompanionApp(userId, packageName);
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
}
}
- /**
- * @deprecated Use {@link #disassociate(int)} instead.
- */
- @Deprecated
- public void disassociate(int userId, String packageName, String macAddress) {
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
- packageName, macAddress);
-
- if (association == null) {
- throw new IllegalArgumentException(
- "Association for mac address=[" + macAddress + "] doesn't exist");
- }
-
- mAssociationStore.getAssociationWithCallerChecks(association.getId());
-
- disassociate(association.getId());
- }
-
@SuppressLint("MissingPermission")
private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
return Binder.withCleanCallingIdentity(() -> {
@@ -185,7 +163,7 @@ public class DisassociationProcessor {
() -> mActivityManager.addOnUidImportanceListener(
mOnPackageVisibilityChangeListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
Slog.e(TAG, "Failed to start listening to uid importance changes.");
}
}
@@ -201,34 +179,6 @@ public class DisassociationProcessor {
}
/**
- * Remove idle self-managed associations.
- */
- public void removeIdleSelfManagedAssociations() {
- Slog.i(TAG, "Removing idle self-managed associations.");
-
- final long currentTime = System.currentTimeMillis();
- long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
- if (removalWindow <= 0) {
- // 0 or negative values indicate that the sysprop was never set or should be ignored.
- removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
- }
-
- for (AssociationInfo association : mAssociationStore.getAssociations()) {
- if (!association.isSelfManaged()) continue;
-
- final boolean isInactive =
- currentTime - association.getLastTimeConnectedMs() >= removalWindow;
- if (!isInactive) continue;
-
- final int id = association.getId();
-
- Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
- + "].");
- disassociate(id);
- }
- }
-
- /**
* An OnUidImportanceListener class which watches the importance of the packages.
* In this class, we ONLY interested in the importance of the running process is greater than
* {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}.
diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index b509e71626ea..f28731548dcc 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -30,7 +30,7 @@ import com.android.server.LocalServices;
import com.android.server.companion.CompanionDeviceManagerServiceInternal;
/**
- * A Job Service responsible for clean up self-managed associations if it's idle for 90 days.
+ * A Job Service responsible for clean up idle self-managed associations.
*
* The job will be executed only if the device is charging and in idle mode due to the application
* will be killed if association/role are revoked. See {@link DisassociationProcessor}
@@ -45,10 +45,10 @@ public class InactiveAssociationsRemovalService extends JobService {
@Override
public boolean onStartJob(final JobParameters params) {
Slog.i(TAG, "Execute the Association Removal job");
-
+ // Special policy for selfManaged that need to revoke associations if the device
+ // does not connect for 90 days.
LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
.removeInactiveSelfManagedAssociations();
-
jobFinished(params, false);
return true;
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9069689ee5eb..c5ca0bf7e9c5 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -31,6 +31,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
+import android.companion.DeviceNotAssociatedException;
import android.companion.IOnMessageReceivedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
@@ -55,6 +56,7 @@ import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
+import com.android.server.companion.utils.PermissionsUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
@@ -118,10 +120,28 @@ public class SystemDataTransferProcessor {
}
/**
+ * Resolve the requested association, throwing if the caller doesn't have
+ * adequate permissions.
+ */
+ @NonNull
+ private AssociationInfo resolveAssociation(String packageName, int userId,
+ int associationId) {
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
+ if (association == null) {
+ throw new DeviceNotAssociatedException("Association "
+ + associationId + " is not associated with the app " + packageName
+ + " for user " + userId);
+ }
+ return association;
+ }
+
+ /**
* Return whether the user has consented to the permission transfer for the association.
*/
- public boolean isPermissionTransferUserConsented(int associationId) {
- mAssociationStore.getAssociationWithCallerChecks(associationId);
+ public boolean isPermissionTransferUserConsented(String packageName, @UserIdInt int userId,
+ int associationId) {
+ resolveAssociation(packageName, userId, associationId);
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
@@ -147,8 +167,7 @@ public class SystemDataTransferProcessor {
return null;
}
- final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
+ final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ "] associationId [" + associationId + "]");
@@ -188,7 +207,7 @@ public class SystemDataTransferProcessor {
Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
+ "] userId [" + userId + "] associationId [" + associationId + "]");
- mAssociationStore.getAssociationWithCallerChecks(associationId);
+ final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
// Check if the request has been consented by the user.
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
@@ -220,20 +239,24 @@ public class SystemDataTransferProcessor {
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(true);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
+ Binder.withCleanCallingIdentity(() -> {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(true);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
+ });
}
/**
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(false);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
+ Binder.withCleanCallingIdentity(() -> {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(false);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
+ });
}
/**
@@ -241,17 +264,18 @@ public class SystemDataTransferProcessor {
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- int userId = mAssociationStore.getAssociationWithCallerChecks(associationId)
- .getUserId();
- List<SystemDataTransferRequest> requests =
- mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
- associationId);
- for (SystemDataTransferRequest request : requests) {
- if (request instanceof PermissionSyncRequest) {
- return (PermissionSyncRequest) request;
+ return Binder.withCleanCallingIdentity(() -> {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ List<SystemDataTransferRequest> requests =
+ mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+ associationId);
+ for (SystemDataTransferRequest request : requests) {
+ if (request instanceof PermissionSyncRequest) {
+ return (PermissionSyncRequest) request;
+ }
}
- }
- return null;
+ return null;
+ });
}
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 9c37881499bd..c89ce11c169d 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -33,7 +33,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 2d345c48a8eb..cb363a7c9d7f 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -19,7 +19,7 @@ package com.android.server.companion.presence;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import android.annotation.NonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
deleted file mode 100644
index b6348ea9594d..000000000000
--- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.presence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-import android.companion.CompanionDeviceService;
-import android.companion.DevicePresenceEvent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Handler;
-import android.util.Pair;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.PerUser;
-import com.android.server.companion.CompanionDeviceManagerService;
-import com.android.server.companion.utils.PackageUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Manages communication with companion applications via
- * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
- * the services, maintaining the connection (the binding), and invoking callback methods such as
- * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
- * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
- * application process.
- *
- * <p>
- * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be
- * utilized by {@link CompanionDeviceManagerService}):
- * <ul>
- * <li> {@link #bindCompanionApp(int, String, boolean, CompanionServiceConnector.Listener)}
- * <li> {@link #unbindCompanionApp(int, String)}
- * <li> {@link #isCompanionApplicationBound(int, String)}
- * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
- * </ul>
- *
- * @see CompanionDeviceService
- * @see android.companion.ICompanionDeviceService
- * @see CompanionServiceConnector
- */
-@SuppressLint("LongLogTag")
-public class CompanionAppBinder {
- private static final String TAG = "CDM_CompanionAppBinder";
-
- private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final CompanionServicesRegister mCompanionServicesRegister;
-
- @NonNull
- @GuardedBy("mBoundCompanionApplications")
- private final Map<Pair<Integer, String>, List<CompanionServiceConnector>>
- mBoundCompanionApplications;
- @NonNull
- @GuardedBy("mScheduledForRebindingCompanionApplications")
- private final Set<Pair<Integer, String>> mScheduledForRebindingCompanionApplications;
-
- public CompanionAppBinder(@NonNull Context context) {
- mContext = context;
- mCompanionServicesRegister = new CompanionServicesRegister();
- mBoundCompanionApplications = new HashMap<>();
- mScheduledForRebindingCompanionApplications = new HashSet<>();
- }
-
- /**
- * On package changed.
- */
- public void onPackagesChanged(@UserIdInt int userId) {
- mCompanionServicesRegister.invalidate(userId);
- }
-
- /**
- * CDM binds to the companion app.
- */
- public void bindCompanionApp(@UserIdInt int userId, @NonNull String packageName,
- boolean isSelfManaged, CompanionServiceConnector.Listener listener) {
- Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=["
- + isSelfManaged + "]...");
-
- final List<ComponentName> companionServices =
- mCompanionServicesRegister.forPackage(userId, packageName);
- if (companionServices.isEmpty()) {
- Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
- + "eligible CompanionDeviceService not found.\n"
- + "A CompanionDeviceService should declare an intent-filter for "
- + "\"android.companion.CompanionDeviceService\" action and require "
- + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
- return;
- }
-
- final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>();
- synchronized (mBoundCompanionApplications) {
- if (mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) {
- Slog.w(TAG, "The package is ALREADY bound.");
- return;
- }
-
- for (int i = 0; i < companionServices.size(); i++) {
- boolean isPrimary = i == 0;
- serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId,
- companionServices.get(i), isSelfManaged, isPrimary));
- }
-
- mBoundCompanionApplications.put(new Pair<>(userId, packageName), serviceConnectors);
- }
-
- // Set listeners for both Primary and Secondary connectors.
- for (CompanionServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.setListener(listener);
- }
-
- // Now "bind" all the connectors: the primary one and the rest of them.
- for (CompanionServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.connect();
- }
- }
-
- /**
- * CDM unbinds the companion app.
- */
- public void unbindCompanionApp(@UserIdInt int userId, @NonNull String packageName) {
- Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]...");
-
- final List<CompanionServiceConnector> serviceConnectors;
-
- synchronized (mBoundCompanionApplications) {
- serviceConnectors = mBoundCompanionApplications.remove(new Pair<>(userId, packageName));
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
- }
-
- if (serviceConnectors == null) {
- Slog.e(TAG, "The package is not bound.");
- return;
- }
-
- for (CompanionServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.postUnbind();
- }
- }
-
- /**
- * @return whether the companion application is bound now.
- */
- public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
- synchronized (mBoundCompanionApplications) {
- return mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName));
- }
- }
-
- /**
- * Remove bound apps for package.
- */
- public void removePackage(int userId, String packageName) {
- synchronized (mBoundCompanionApplications) {
- mBoundCompanionApplications.remove(new Pair<>(userId, packageName));
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
- }
- }
-
- /**
- * Schedule rebinding for the package.
- */
- public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
- CompanionServiceConnector serviceConnector) {
- Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
-
- if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
- Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
- + serviceConnector.getComponentName());
- return;
- }
-
- if (serviceConnector.isPrimary()) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.add(new Pair<>(userId, packageName));
- }
- }
-
- // Rebinding in 10 seconds.
- Handler.getMain().postDelayed(() ->
- onRebindingCompanionApplicationTimeout(userId, packageName,
- serviceConnector),
- REBIND_TIMEOUT);
- }
-
- private boolean isRebindingCompanionApplicationScheduled(
- @UserIdInt int userId, @NonNull String packageName) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- return mScheduledForRebindingCompanionApplications.contains(
- new Pair<>(userId, packageName));
- }
- }
-
- private void onRebindingCompanionApplicationTimeout(
- @UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionServiceConnector serviceConnector) {
- // Re-mark the application is bound.
- if (serviceConnector.isPrimary()) {
- synchronized (mBoundCompanionApplications) {
- if (!mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) {
- List<CompanionServiceConnector> serviceConnectors =
- Collections.singletonList(serviceConnector);
- mBoundCompanionApplications.put(new Pair<>(userId, packageName),
- serviceConnectors);
- }
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
- }
- }
-
- serviceConnector.connect();
- }
-
- /**
- * Dump bound apps.
- */
- public void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Application Controller: \n");
-
- synchronized (mBoundCompanionApplications) {
- out.append(" Bound Companion Applications: ");
- if (mBoundCompanionApplications.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (Map.Entry<Pair<Integer, String>, List<CompanionServiceConnector>> entry :
- mBoundCompanionApplications.entrySet()) {
- out.append("<u").append(String.valueOf(entry.getKey().first)).append(", ")
- .append(entry.getKey().second).append(">");
- for (CompanionServiceConnector serviceConnector : entry.getValue()) {
- out.append(", isPrimary=").append(
- String.valueOf(serviceConnector.isPrimary()));
- }
- }
- }
- }
-
- out.append(" Companion Applications Scheduled For Rebinding: ");
- synchronized (mScheduledForRebindingCompanionApplications) {
- if (mScheduledForRebindingCompanionApplications.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (Pair<Integer, String> app : mScheduledForRebindingCompanionApplications) {
- out.append("<u").append(String.valueOf(app.first)).append(", ")
- .append(app.second).append(">");
- }
- }
- }
- }
-
- @Nullable
- CompanionServiceConnector getPrimaryServiceConnector(
- @UserIdInt int userId, @NonNull String packageName) {
- final List<CompanionServiceConnector> connectors;
- synchronized (mBoundCompanionApplications) {
- connectors = mBoundCompanionApplications.get(new Pair<>(userId, packageName));
- }
- return connectors != null ? connectors.get(0) : null;
- }
-
- private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
- @Override
- public synchronized @NonNull Map<String, List<ComponentName>> forUser(
- @UserIdInt int userId) {
- return super.forUser(userId);
- }
-
- synchronized @NonNull List<ComponentName> forPackage(
- @UserIdInt int userId, @NonNull String packageName) {
- return forUser(userId).getOrDefault(packageName, Collections.emptyList());
- }
-
- synchronized void invalidate(@UserIdInt int userId) {
- remove(userId);
- }
-
- @Override
- protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
- return PackageUtils.getCompanionServicesForUser(mContext, userId);
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
new file mode 100644
index 000000000000..7a1a83f53315
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.presence;
+
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.association.AssociationStore;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
+ * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
+ * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
+ BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CDM_CompanionDevicePresenceMonitor";
+
+ /** Callback for notifying about changes to status of companion devices. */
+ public interface Callback {
+ /** Invoked when companion device is found nearby or connects. */
+ void onDeviceAppeared(int associationId);
+
+ /** Invoked when a companion device no longer seen nearby or disconnects. */
+ void onDeviceDisappeared(int associationId);
+
+ /** Invoked when device has corresponding event changes. */
+ void onDevicePresenceEvent(int associationId, int event);
+
+ /** Invoked when device has corresponding event changes base on the UUID */
+ void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
+ }
+
+ private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
+ private final @NonNull Callback mCallback;
+ private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ private final @NonNull BleCompanionDeviceScanner mBleScanner;
+
+ // NOTE: Same association may appear in more than one of the following sets at the same time.
+ // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+ // companion applications, while at the same be connected via BT, or detected nearby by BLE
+ // scanner)
+ private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
+ private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
+ private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
+ @GuardedBy("mBtDisconnectedDevices")
+ private final @NonNull Set<Integer> mBtDisconnectedDevices = new HashSet<>();
+
+ // A map to track device presence within 10 seconds of Bluetooth disconnection.
+ // The key is the association ID, and the boolean value indicates if the device
+ // was detected again within that time frame.
+ @GuardedBy("mBtDisconnectedDevices")
+ private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
+ new SparseBooleanArray();
+
+ // Tracking "simulated" presence. Used for debugging and testing only.
+ private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+ private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+ new SimulatedDevicePresenceSchedulerHelper();
+
+ private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
+ new BleDeviceDisappearedScheduler();
+
+ public CompanionDevicePresenceMonitor(UserManager userManager,
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
+ mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
+ mCallback = callback;
+ mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
+ associationStore, mObservableUuidStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ mBleScanner = new BleCompanionDeviceScanner(associationStore,
+ /* BleCompanionDeviceScanner.Callback */ this);
+ }
+
+ /** Initialize {@link CompanionDevicePresenceMonitor} */
+ public void init(Context context) {
+ if (DEBUG) Log.i(TAG, "init()");
+
+ final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter != null) {
+ mBtConnectionListener.init(btAdapter);
+ mBleScanner.init(context, btAdapter);
+ } else {
+ Log.w(TAG, "BluetoothAdapter is NOT available.");
+ }
+
+ mAssociationStore.registerLocalListener(this);
+ }
+
+ /**
+ * @return current connected UUID devices.
+ */
+ public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
+ return mConnectedUuidDevices;
+ }
+
+ /**
+ * Remove current connected UUID device.
+ */
+ public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
+ mConnectedUuidDevices.remove(uuid);
+ }
+
+ /**
+ * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
+ * or devices is connected (for Bluetooth); or reported (by the application) to be
+ * nearby (for "self-managed" associations).
+ */
+ public boolean isDevicePresent(int associationId) {
+ return mReportedSelfManagedDevices.contains(associationId)
+ || mConnectedBtDevices.contains(associationId)
+ || mNearbyBleDevices.contains(associationId)
+ || mSimulated.contains(associationId);
+ }
+
+ /**
+ * @return whether the current uuid to be observed is present.
+ */
+ public boolean isDeviceUuidPresent(ParcelUuid uuid) {
+ return mConnectedUuidDevices.contains(uuid);
+ }
+
+ /**
+ * @return whether the current device is BT connected and had already reported to the app.
+ */
+
+ public boolean isBtConnected(int associationId) {
+ return mConnectedBtDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device in BLE range and had already reported to the app.
+ */
+ public boolean isBlePresent(int associationId) {
+ return mNearbyBleDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device had been already reported by the simulator.
+ */
+ public boolean isSimulatePresent(int associationId) {
+ return mSimulated.contains(associationId);
+ }
+
+ /**
+ * Marks a "self-managed" device as connected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
+ */
+ public void onSelfManagedDeviceConnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_APPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
+ */
+ public void onSelfManagedDeviceDisconnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected when binderDied.
+ */
+ public void onSelfManagedDeviceReporterBinderDied(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceConnected(int associationId) {
+ synchronized (mBtDisconnectedDevices) {
+ // A device is considered reconnected within 10 seconds if a pending BLE lost report is
+ // followed by a detected Bluetooth connection.
+ boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
+ if (isReconnected) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+
+ Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ + "associationId( " + associationId + " )");
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+
+ // Stop the BLE scan if all devices report BT connected status and BLE was present.
+ if (canStopBleScan()) {
+ mBleScanner.stopScanIfNeeded();
+ }
+
+ }
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
+ + "associationId( " + associationId + " )");
+ // Start BLE scanning when the device is disconnected.
+ mBleScanner.startScan();
+
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
+ // If current device is BLE present but BT is disconnected , means it will be
+ // potentially out of range later. Schedule BLE disappeared callback.
+ if (isBlePresent(associationId)) {
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.add(associationId);
+ }
+ mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
+ }
+ }
+
+ @Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+ final ParcelUuid parcelUuid = uuid.getUuid();
+
+ switch(event) {
+ case EVENT_BT_CONNECTED:
+ boolean added = mConnectedUuidDevices.add(parcelUuid);
+
+ if (!added) {
+ Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as "
+ + "present by this event=" + event);
+ }
+
+ break;
+ case EVENT_BT_DISCONNECTED:
+ final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
+
+ if (!removed) {
+ Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported "
+ + "as present by this event= " + event);
+
+ return;
+ }
+
+ break;
+ }
+
+ mCallback.onDevicePresenceEventByUuid(uuid, event);
+ }
+
+
+ @Override
+ public void onBleCompanionDeviceFound(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
+ if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+ }
+ }
+
+ @Override
+ public void onBleCompanionDeviceLost(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEvent(int associationId, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ simulateDeviceAppeared(associationId, event);
+ break;
+ case EVENT_BT_CONNECTED:
+ onBluetoothCompanionDeviceConnected(associationId);
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ simulateDeviceDisappeared(associationId, event);
+ break;
+ case EVENT_BT_DISCONNECTED:
+ onBluetoothCompanionDeviceDisconnected(associationId);
+ break;
+ default:
+ throw new IllegalArgumentException("Event: " + event + "is not supported");
+ }
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ onDevicePresenceEventByUuid(uuid, event);
+ }
+
+ private void simulateDeviceAppeared(int associationId, int state) {
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ }
+
+ private void simulateDeviceDisappeared(int associationId, int state) {
+ mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ }
+
+ private void enforceAssociationExists(int associationId) {
+ if (mAssociationStore.getAssociationById(associationId) == null) {
+ throw new IllegalArgumentException(
+ "Association with id " + associationId + " does not exist.");
+ }
+ }
+
+ private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
+ int associationId, int event) {
+ Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
+
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ synchronized (mBtDisconnectedDevices) {
+ // If a BLE device is detected within 10 seconds after BT is disconnected,
+ // flag it as BLE is present.
+ if (mBtDisconnectedDevices.contains(associationId)) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is present,"
+ + " do not need to send the callback with event ( "
+ + EVENT_BLE_APPEARED + " ).");
+ mBtDisconnectedDevicesBlePresence.append(associationId, true);
+ }
+ }
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
+ final boolean added = presentDevicesForSource.add(associationId);
+
+ if (!added) {
+ Slog.w(TAG, "Association with id "
+ + associationId + " is ALREADY reported as "
+ + "present by this source, event=" + event);
+ }
+
+ mCallback.onDeviceAppeared(associationId);
+
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
+ final boolean removed = presentDevicesForSource.remove(associationId);
+
+ if (!removed) {
+ Slog.w(TAG, "Association with id " + associationId + " was NOT reported "
+ + "as present by this source, event= " + event);
+
+ return;
+ }
+
+ mCallback.onDeviceDisappeared(associationId);
+
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + " is not supported");
+ return;
+ }
+
+ mCallback.onDevicePresenceEvent(associationId, event);
+ }
+
+ /**
+ * Implements
+ * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+ */
+ @Override
+ public void onAssociationRemoved(@NonNull AssociationInfo association) {
+ final int id = association.getId();
+ if (DEBUG) {
+ Log.i(TAG, "onAssociationRemoved() id=" + id);
+ Log.d(TAG, " > association=" + association);
+ }
+
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
+ mSimulated.remove(id);
+ mBtDisconnectedDevices.remove(id);
+ mBtDisconnectedDevicesBlePresence.delete(id);
+
+ // Do NOT call mCallback.onDeviceDisappeared()!
+ // CompanionDeviceManagerService will know that the association is removed, and will do
+ // what's needed.
+ }
+
+ /**
+ * Return a set of devices that pending to report connectivity
+ */
+ public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
+ synchronized (mBtConnectionListener.mPendingConnectedDevices) {
+ return mBtConnectionListener.mPendingConnectedDevices;
+ }
+ }
+
+ private static void enforceCallerShellOrRoot() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+ throw new SecurityException("Caller is neither Shell nor Root");
+ }
+
+ /**
+ * The BLE scan can be only stopped if all the devices have been reported
+ * BT connected and BLE presence and are not pending to report BLE lost.
+ */
+ private boolean canStopBleScan() {
+ for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
+ int id = ai.getId();
+ synchronized (mBtDisconnectedDevices) {
+ if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
+ && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
+ Slog.i(TAG, "The BLE scan cannot be stopped, "
+ + "device( " + id + " ) is not yet connected "
+ + "OR the BLE is not current present Or is pending to report BLE lost");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Dumps system information about devices that are marked as "present".
+ */
+ public void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Present: ");
+ if (mConnectedBtDevices.isEmpty()
+ && mNearbyBleDevices.isEmpty()
+ && mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ return;
+ } else {
+ out.append("\n");
+ }
+
+ out.append(" Connected Bluetooth Devices: ");
+ if (mConnectedBtDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mConnectedBtDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Nearby BLE Devices: ");
+ if (mNearbyBleDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mNearbyBleDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Self-Reported Devices: ");
+ if (mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mReportedSelfManagedDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+ }
+
+ private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+ SimulatedDevicePresenceSchedulerHelper() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ // First, unschedule if it was scheduled previously.
+ if (hasMessages(/* what */ associationId)) {
+ removeMessages(/* what */ associationId);
+ }
+
+ sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+ }
+
+ void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ removeMessages(/* what */ associationId);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ if (mSimulated.contains(associationId)) {
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
+ }
+ }
+ }
+
+ private class BleDeviceDisappearedScheduler extends Handler {
+ BleDeviceDisappearedScheduler() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleBleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ removeMessages(associationId);
+ }
+ Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
+ sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
+ }
+
+ void unScheduleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+
+ removeMessages(associationId);
+ }
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
+ associationId);
+ // If a device hasn't reported after 10 seconds and is not currently present,
+ // assume BLE is lost and trigger the onDeviceEvent callback with the
+ // EVENT_BLE_DISAPPEARED event.
+ if (mBtDisconnectedDevices.contains(associationId)
+ && !isCurrentPresent) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
+ + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
deleted file mode 100644
index 642460e64216..000000000000
--- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
+++ /dev/null
@@ -1,1042 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.presence;
-
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
-import static android.companion.DevicePresenceEvent.NO_ASSOCIATION;
-import static android.os.Process.ROOT_UID;
-import static android.os.Process.SHELL_UID;
-
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObserveDevicePresenceByUuid;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.annotation.UserIdInt;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.companion.AssociationInfo;
-import android.companion.DeviceNotAssociatedException;
-import android.companion.DevicePresenceEvent;
-import android.companion.ObservingDevicePresenceRequest;
-import android.content.Context;
-import android.hardware.power.Mode;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManagerInternal;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.association.AssociationStore;
-
-import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Class responsible for monitoring companion devices' "presence" status (i.e.
- * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
- *
- * <p>
- * Should only be used by
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * to which it provides the following API:
- * <ul>
- * <li> {@link #onSelfManagedDeviceConnected(int)}
- * <li> {@link #onSelfManagedDeviceDisconnected(int)}
- * <li> {@link #isDevicePresent(int)}
- * </ul>
- */
-@SuppressLint("LongLogTag")
-public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
- BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
- static final boolean DEBUG = false;
- private static final String TAG = "CDM_DevicePresenceProcessor";
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final CompanionAppBinder mCompanionAppBinder;
- @NonNull
- private final AssociationStore mAssociationStore;
- @NonNull
- private final ObservableUuidStore mObservableUuidStore;
- @NonNull
- private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
- @NonNull
- private final BleCompanionDeviceScanner mBleScanner;
- @NonNull
- private final PowerManagerInternal mPowerManagerInternal;
-
- // NOTE: Same association may appear in more than one of the following sets at the same time.
- // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
- // companion applications, while at the same be connected via BT, or detected nearby by BLE
- // scanner)
- @NonNull
- private final Set<Integer> mConnectedBtDevices = new HashSet<>();
- @NonNull
- private final Set<Integer> mNearbyBleDevices = new HashSet<>();
- @NonNull
- private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
- @NonNull
- private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
- @NonNull
- @GuardedBy("mBtDisconnectedDevices")
- private final Set<Integer> mBtDisconnectedDevices = new HashSet<>();
-
- // A map to track device presence within 10 seconds of Bluetooth disconnection.
- // The key is the association ID, and the boolean value indicates if the device
- // was detected again within that time frame.
- @GuardedBy("mBtDisconnectedDevices")
- private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
- new SparseBooleanArray();
-
- // Tracking "simulated" presence. Used for debugging and testing only.
- private final @NonNull Set<Integer> mSimulated = new HashSet<>();
- private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
- new SimulatedDevicePresenceSchedulerHelper();
-
- private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
- new BleDeviceDisappearedScheduler();
-
- public DevicePresenceProcessor(@NonNull Context context,
- @NonNull CompanionAppBinder companionAppBinder,
- UserManager userManager,
- @NonNull AssociationStore associationStore,
- @NonNull ObservableUuidStore observableUuidStore,
- @NonNull PowerManagerInternal powerManagerInternal) {
- mContext = context;
- mCompanionAppBinder = companionAppBinder;
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, mObservableUuidStore,
- /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
- mBleScanner = new BleCompanionDeviceScanner(associationStore,
- /* BleCompanionDeviceScanner.Callback */ this);
- mPowerManagerInternal = powerManagerInternal;
- }
-
- /** Initialize {@link DevicePresenceProcessor} */
- public void init(Context context) {
- if (DEBUG) Slog.i(TAG, "init()");
-
- final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
- if (btAdapter != null) {
- mBtConnectionListener.init(btAdapter);
- mBleScanner.init(context, btAdapter);
- } else {
- Slog.w(TAG, "BluetoothAdapter is NOT available.");
- }
-
- mAssociationStore.registerLocalListener(this);
- }
-
- /**
- * Process device presence start request.
- */
- public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
- String packageName, int userId) {
- Slog.i(TAG,
- "Start observing request=[" + request + "] for userId=[" + userId + "], package=["
- + packageName + "]...");
- final ParcelUuid requestUuid = request.getUuid();
-
- if (requestUuid != null) {
- enforceCallerCanObserveDevicePresenceByUuid(mContext);
-
- // If it's already being observed, then no-op.
- if (mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
- Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
- + userId + "] is already being observed.");
- return;
- }
-
- final ObservableUuid observableUuid = new ObservableUuid(userId, requestUuid,
- packageName, System.currentTimeMillis());
- mObservableUuidStore.writeObservableUuid(userId, observableUuid);
- } else {
- final int associationId = request.getAssociationId();
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
-
- // If it's already being observed, then no-op.
- if (association.isNotifyOnDeviceNearby()) {
- Slog.i(TAG, "Associated device id=[" + association.getId()
- + "] is already being observed. No-op.");
- return;
- }
-
- association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(true)
- .build();
- mAssociationStore.updateAssociation(association);
-
- // Send callback immediately if the device is present.
- if (isDevicePresent(associationId)) {
- Slog.i(TAG, "Device is already present. Triggering callback.");
- if (isBlePresent(associationId)) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
- } else if (isBtConnected(associationId)) {
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
- } else if (isSimulatePresent(associationId)) {
- onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_APPEARED);
- }
- }
- }
-
- Slog.i(TAG, "Registered device presence listener.");
- }
-
- /**
- * Process device presence stop request.
- */
- public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
- String packageName, int userId) {
- Slog.i(TAG,
- "Stop observing request=[" + request + "] for userId=[" + userId + "], package=["
- + packageName + "]...");
-
- final ParcelUuid requestUuid = request.getUuid();
-
- if (requestUuid != null) {
- enforceCallerCanObserveDevicePresenceByUuid(mContext);
-
- if (!mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
- Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
- + userId + "] is already not being observed.");
- return;
- }
-
- mObservableUuidStore.removeObservableUuid(userId, requestUuid, packageName);
- removeCurrentConnectedUuidDevice(requestUuid);
- } else {
- final int associationId = request.getAssociationId();
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
-
- // If it's already being observed, then no-op.
- if (!association.isNotifyOnDeviceNearby()) {
- Slog.i(TAG, "Associated device id=[" + association.getId()
- + "] is already not being observed. No-op.");
- return;
- }
-
- association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(false)
- .build();
- mAssociationStore.updateAssociation(association);
- }
-
- Slog.i(TAG, "Unregistered device presence listener.");
-
- // If last listener is unregistered, then unbind application.
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppBinder.unbindCompanionApp(userId, packageName);
- }
- }
-
- /**
- * For legacy device presence below Android V.
- *
- * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
- */
- @Deprecated
- public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
- throws RemoteException {
- Slog.i(TAG,
- "Start observing device=[" + deviceAddress + "] for userId=[" + userId
- + "], package=["
- + packageName + "]...");
-
- enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
-
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
- packageName, deviceAddress);
-
- if (association == null) {
- throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
- + " is not associated with device " + deviceAddress
- + " for user " + userId));
- }
-
- startObservingDevicePresence(
- new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
- .build(), packageName, userId);
- }
-
- /**
- * For legacy device presence below Android V.
- *
- * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
- */
- @Deprecated
- public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
- throws RemoteException {
- Slog.i(TAG,
- "Stop observing device=[" + deviceAddress + "] for userId=[" + userId
- + "], package=["
- + packageName + "]...");
-
- enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
-
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
- packageName, deviceAddress);
-
- if (association == null) {
- throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
- + " is not associated with device " + deviceAddress
- + " for user " + userId));
- }
-
- stopObservingDevicePresence(
- new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
- .build(), packageName, userId);
- }
-
- /**
- * @return whether the package should be bound (i.e. at least one of the devices associated with
- * the package is currently present OR the UUID to be observed by this package is
- * currently present).
- */
- private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
- final List<AssociationInfo> packageAssociations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo association : packageAssociations) {
- if (!association.shouldBindWhenPresent()) continue;
- if (isDevicePresent(association.getId())) return true;
- }
-
- for (ObservableUuid uuid : observableUuids) {
- if (isDeviceUuidPresent(uuid.getUuid())) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Bind the system to the app if it's not bound.
- *
- * Set bindImportant to true when the association is self-managed to avoid the target service
- * being killed.
- */
- private void bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant) {
- if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
- mCompanionAppBinder.bindCompanionApp(
- userId, packageName, bindImportant, this::onBinderDied);
- } else {
- Slog.i(TAG,
- "UserId=[" + userId + "], packageName=[" + packageName + "] is already bound.");
- }
- }
-
- /**
- * @return current connected UUID devices.
- */
- public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
- return mConnectedUuidDevices;
- }
-
- /**
- * Remove current connected UUID device.
- */
- public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
- mConnectedUuidDevices.remove(uuid);
- }
-
- /**
- * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
- * or devices is connected (for Bluetooth); or reported (by the application) to be
- * nearby (for "self-managed" associations).
- */
- public boolean isDevicePresent(int associationId) {
- return mReportedSelfManagedDevices.contains(associationId)
- || mConnectedBtDevices.contains(associationId)
- || mNearbyBleDevices.contains(associationId)
- || mSimulated.contains(associationId);
- }
-
- /**
- * @return whether the current uuid to be observed is present.
- */
- public boolean isDeviceUuidPresent(ParcelUuid uuid) {
- return mConnectedUuidDevices.contains(uuid);
- }
-
- /**
- * @return whether the current device is BT connected and had already reported to the app.
- */
-
- public boolean isBtConnected(int associationId) {
- return mConnectedBtDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device in BLE range and had already reported to the app.
- */
- public boolean isBlePresent(int associationId) {
- return mNearbyBleDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device had been already reported by the simulator.
- */
- public boolean isSimulatePresent(int associationId) {
- return mSimulated.contains(associationId);
- }
-
- /**
- * Marks a "self-managed" device as connected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService
- * CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int)
- * notifyDeviceAppeared()}
- */
- public void onSelfManagedDeviceConnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_APPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService
- * CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int)
- * notifyDeviceDisappeared()}
- */
- public void onSelfManagedDeviceDisconnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected when binderDied.
- */
- public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- @Override
- public void onBluetoothCompanionDeviceConnected(int associationId) {
- synchronized (mBtDisconnectedDevices) {
- // A device is considered reconnected within 10 seconds if a pending BLE lost report is
- // followed by a detected Bluetooth connection.
- boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
- if (isReconnected) {
- Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
-
- Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
- + "associationId( " + associationId + " )");
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
-
- // Stop the BLE scan if all devices report BT connected status and BLE was present.
- if (canStopBleScan()) {
- mBleScanner.stopScanIfNeeded();
- }
-
- }
- }
-
- @Override
- public void onBluetoothCompanionDeviceDisconnected(int associationId) {
- Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
- + "associationId( " + associationId + " )");
- // Start BLE scanning when the device is disconnected.
- mBleScanner.startScan();
-
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
- // If current device is BLE present but BT is disconnected , means it will be
- // potentially out of range later. Schedule BLE disappeared callback.
- if (isBlePresent(associationId)) {
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.add(associationId);
- }
- mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
- }
- }
-
-
- @Override
- public void onBleCompanionDeviceFound(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
- if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
- }
- }
-
- @Override
- public void onBleCompanionDeviceLost(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- public void simulateDeviceEvent(int associationId, int event) {
- // IMPORTANT: this API should only be invoked via the
- // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
- // make this call are SHELL and ROOT.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- // Make sure the association exists.
- enforceAssociationExists(associationId);
-
- switch (event) {
- case EVENT_BLE_APPEARED:
- simulateDeviceAppeared(associationId, event);
- break;
- case EVENT_BT_CONNECTED:
- onBluetoothCompanionDeviceConnected(associationId);
- break;
- case EVENT_BLE_DISAPPEARED:
- simulateDeviceDisappeared(associationId, event);
- break;
- case EVENT_BT_DISCONNECTED:
- onBluetoothCompanionDeviceDisconnected(associationId);
- break;
- default:
- throw new IllegalArgumentException("Event: " + event + "is not supported");
- }
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
- // IMPORTANT: this API should only be invoked via the
- // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
- // make this call are SHELL and ROOT.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- onDevicePresenceEventByUuid(uuid, event);
- }
-
- private void simulateDeviceAppeared(int associationId, int state) {
- onDevicePresenceEvent(mSimulated, associationId, state);
- mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- }
-
- private void simulateDeviceDisappeared(int associationId, int state) {
- mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- onDevicePresenceEvent(mSimulated, associationId, state);
- }
-
- private void enforceAssociationExists(int associationId) {
- if (mAssociationStore.getAssociationById(associationId) == null) {
- throw new IllegalArgumentException(
- "Association with id " + associationId + " does not exist.");
- }
- }
-
- private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
- int associationId, int eventType) {
- Slog.i(TAG,
- "onDevicePresenceEvent() id=[" + associationId + "], event=[" + eventType + "]...");
-
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (association == null) {
- Slog.e(TAG, "Association doesn't exist.");
- return;
- }
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final DevicePresenceEvent event = new DevicePresenceEvent(associationId, eventType, null);
-
- if (eventType == EVENT_BLE_APPEARED) {
- synchronized (mBtDisconnectedDevices) {
- // If a BLE device is detected within 10 seconds after BT is disconnected,
- // flag it as BLE is present.
- if (mBtDisconnectedDevices.contains(associationId)) {
- Slog.i(TAG, "Device ( " + associationId + " ) is present,"
- + " do not need to send the callback with event ( "
- + EVENT_BLE_APPEARED + " ).");
- mBtDisconnectedDevicesBlePresence.append(associationId, true);
- }
- }
- }
-
- switch (eventType) {
- case EVENT_BLE_APPEARED:
- case EVENT_BT_CONNECTED:
- case EVENT_SELF_MANAGED_APPEARED:
- final boolean added = presentDevicesForSource.add(associationId);
- if (!added) {
- Slog.w(TAG, "The association is already present.");
- }
-
- if (association.shouldBindWhenPresent()) {
- bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
- } else {
- return;
- }
-
- if (association.isSelfManaged() || added) {
- notifyDevicePresenceEvent(userId, packageName, event);
- // Also send the legacy callback.
- legacyNotifyDevicePresenceEvent(association, true);
- }
- break;
- case EVENT_BLE_DISAPPEARED:
- case EVENT_BT_DISCONNECTED:
- case EVENT_SELF_MANAGED_DISAPPEARED:
- final boolean removed = presentDevicesForSource.remove(associationId);
- if (!removed) {
- Slog.w(TAG, "The association is already NOT present.");
- }
-
- if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
- Slog.e(TAG, "Package is not bound");
- return;
- }
-
- if (association.isSelfManaged() || removed) {
- notifyDevicePresenceEvent(userId, packageName, event);
- // Also send the legacy callback.
- legacyNotifyDevicePresenceEvent(association, false);
- }
-
- // Check if there are other devices associated to the app that are present.
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppBinder.unbindCompanionApp(userId, packageName);
- }
- break;
- default:
- Slog.e(TAG, "Event: " + eventType + " is not supported.");
- break;
- }
- }
-
- @Override
- public void onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType) {
- Slog.i(TAG, "onDevicePresenceEventByUuid ObservableUuid=[" + uuid + "], event=[" + eventType
- + "]...");
-
- final ParcelUuid parcelUuid = uuid.getUuid();
- final String packageName = uuid.getPackageName();
- final int userId = uuid.getUserId();
- final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType,
- parcelUuid);
-
- switch (eventType) {
- case EVENT_BT_CONNECTED:
- boolean added = mConnectedUuidDevices.add(parcelUuid);
- if (!added) {
- Slog.w(TAG, "This device is already connected.");
- }
-
- bindApplicationIfNeeded(userId, packageName, false);
-
- notifyDevicePresenceEvent(userId, packageName, event);
- break;
- case EVENT_BT_DISCONNECTED:
- final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
- if (!removed) {
- Slog.w(TAG, "This device is already disconnected.");
- return;
- }
-
- if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
- Slog.e(TAG, "Package is not bound.");
- return;
- }
-
- notifyDevicePresenceEvent(userId, packageName, event);
-
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppBinder.unbindCompanionApp(userId, packageName);
- }
- break;
- default:
- Slog.e(TAG, "Event: " + eventType + " is not supported");
- break;
- }
- }
-
- /**
- * Notify device presence event to the app.
- *
- * @deprecated Use {@link #notifyDevicePresenceEvent(int, String, DevicePresenceEvent)} instead.
- */
- @Deprecated
- private void legacyNotifyDevicePresenceEvent(AssociationInfo association,
- boolean isAppeared) {
- Slog.i(TAG, "legacyNotifyDevicePresenceEvent() association=[" + association.toShortString()
- + "], isAppeared=[" + isAppeared + "]");
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- final CompanionServiceConnector primaryServiceConnector =
- mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "Package is not bound.");
- return;
- }
-
- if (isAppeared) {
- primaryServiceConnector.postOnDeviceAppeared(association);
- } else {
- primaryServiceConnector.postOnDeviceDisappeared(association);
- }
- }
-
- /**
- * Notify the device presence event to the app.
- */
- private void notifyDevicePresenceEvent(int userId, String packageName,
- DevicePresenceEvent event) {
- Slog.i(TAG,
- "notifyCompanionDevicePresenceEvent userId=[" + userId + "], packageName=["
- + packageName + "], event=[" + event + "]...");
-
- final CompanionServiceConnector primaryServiceConnector =
- mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
-
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "Package is NOT bound.");
- return;
- }
-
- primaryServiceConnector.postOnDevicePresenceEvent(event);
- }
-
- /**
- * Notify the self-managed device presence event to the app.
- */
- public void notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared) {
- Slog.i(TAG, "notifySelfManagedDeviceAppeared() id=" + associationId);
-
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- if (!association.isSelfManaged()) {
- throw new IllegalArgumentException("Association id=[" + associationId
- + "] is not self-managed.");
- }
- // AssociationInfo class is immutable: create a new AssociationInfo object with updated
- // timestamp.
- association = (new AssociationInfo.Builder(association))
- .setLastTimeConnected(System.currentTimeMillis())
- .build();
- mAssociationStore.updateAssociation(association);
-
- if (isAppeared) {
- onSelfManagedDeviceConnected(associationId);
- } else {
- onSelfManagedDeviceDisconnected(associationId);
- }
-
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, isAppeared);
- }
- }
-
- private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionServiceConnector serviceConnector) {
-
- boolean isPrimary = serviceConnector.isPrimary();
- Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
-
- // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
- if (isPrimary) {
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
-
- for (AssociationInfo association : associations) {
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
- break;
- }
- }
-
- mCompanionAppBinder.removePackage(userId, packageName);
- }
-
- // Second: schedule rebinding if needed.
- final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
-
- if (shouldScheduleRebind) {
- mCompanionAppBinder.scheduleRebinding(userId, packageName, serviceConnector);
- }
- }
-
- /**
- * Check if the system should rebind the self-managed secondary services
- * OR non-self-managed services.
- */
- private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
- // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
- // app is uninstalled.
- boolean stillAssociated = false;
- // Make sure to clean up the state for all the associations
- // that associate with this package.
- boolean shouldScheduleRebind = false;
- boolean shouldScheduleRebindForUuid = false;
- final List<ObservableUuid> uuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo ai :
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
- final int associationId = ai.getId();
- stillAssociated = true;
- if (ai.isSelfManaged()) {
- // Do not rebind if primary one is died for selfManaged application.
- if (isPrimary && isDevicePresent(associationId)) {
- onSelfManagedDeviceReporterBinderDied(associationId);
- shouldScheduleRebind = false;
- }
- // Do not rebind if both primary and secondary services are died for
- // selfManaged application.
- shouldScheduleRebind = mCompanionAppBinder.isCompanionApplicationBound(userId,
- packageName);
- } else if (ai.isNotifyOnDeviceNearby()) {
- // Always rebind for non-selfManaged devices.
- shouldScheduleRebind = true;
- }
- }
-
- for (ObservableUuid uuid : uuids) {
- if (isDeviceUuidPresent(uuid.getUuid())) {
- shouldScheduleRebindForUuid = true;
- break;
- }
- }
-
- return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
- }
-
- /**
- * Implements
- * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
- */
- @Override
- public void onAssociationRemoved(@NonNull AssociationInfo association) {
- final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "onAssociationRemoved() id=" + id);
- Log.d(TAG, " > association=" + association);
- }
-
- mConnectedBtDevices.remove(id);
- mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
- mSimulated.remove(id);
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.remove(id);
- mBtDisconnectedDevicesBlePresence.delete(id);
- }
-
- // Do NOT call mCallback.onDeviceDisappeared()!
- // CompanionDeviceManagerService will know that the association is removed, and will do
- // what's needed.
- }
-
- /**
- * Return a set of devices that pending to report connectivity
- */
- public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
- synchronized (mBtConnectionListener.mPendingConnectedDevices) {
- return mBtConnectionListener.mPendingConnectedDevices;
- }
- }
-
- private static void enforceCallerShellOrRoot() {
- final int callingUid = Binder.getCallingUid();
- if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
-
- throw new SecurityException("Caller is neither Shell nor Root");
- }
-
- /**
- * The BLE scan can be only stopped if all the devices have been reported
- * BT connected and BLE presence and are not pending to report BLE lost.
- */
- private boolean canStopBleScan() {
- for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
- int id = ai.getId();
- synchronized (mBtDisconnectedDevices) {
- if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
- && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
- Slog.i(TAG, "The BLE scan cannot be stopped, "
- + "device( " + id + " ) is not yet connected "
- + "OR the BLE is not current present Or is pending to report BLE lost");
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Dumps system information about devices that are marked as "present".
- */
- public void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Present: ");
- if (mConnectedBtDevices.isEmpty()
- && mNearbyBleDevices.isEmpty()
- && mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- return;
- } else {
- out.append("\n");
- }
-
- out.append(" Connected Bluetooth Devices: ");
- if (mConnectedBtDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mConnectedBtDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Nearby BLE Devices: ");
- if (mNearbyBleDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mNearbyBleDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Self-Reported Devices: ");
- if (mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mReportedSelfManagedDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
- }
-
- private class SimulatedDevicePresenceSchedulerHelper extends Handler {
- SimulatedDevicePresenceSchedulerHelper() {
- super(Looper.getMainLooper());
- }
-
- void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- // First, unschedule if it was scheduled previously.
- if (hasMessages(/* what */ associationId)) {
- removeMessages(/* what */ associationId);
- }
-
- sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
- }
-
- void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- removeMessages(/* what */ associationId);
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- if (mSimulated.contains(associationId)) {
- onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
- }
- }
- }
-
- private class BleDeviceDisappearedScheduler extends Handler {
- BleDeviceDisappearedScheduler() {
- super(Looper.getMainLooper());
- }
-
- void scheduleBleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- removeMessages(associationId);
- }
- Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
- sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
- }
-
- void unScheduleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
-
- removeMessages(associationId);
- }
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
- associationId);
- // If a device hasn't reported after 10 seconds and is not currently present,
- // assume BLE is lost and trigger the onDeviceEvent callback with the
- // EVENT_BLE_DISAPPEARED event.
- if (mBtDisconnectedDevices.contains(associationId)
- && !isCurrentPresent) {
- Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
- + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index fa0f6bd92acb..db15da2922cf 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -300,18 +300,4 @@ public class ObservableUuidStore {
return readObservableUuidsFromCache(userId);
}
}
-
- /**
- * Check if a UUID is being observed by the package.
- */
- public boolean isUuidBeingObserved(ParcelUuid uuid, int userId, String packageName) {
- final List<ObservableUuid> uuidsBeingObserved = getObservableUuidsForPackage(userId,
- packageName);
- for (ObservableUuid observableUuid : uuidsBeingObserved) {
- if (observableUuid.getUuid().equals(uuid)) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 697ef87b5a12..793fb7ff74b1 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -46,6 +46,7 @@ import java.util.concurrent.Future;
@SuppressLint("LongLogTag")
public class CompanionTransportManager {
private static final String TAG = "CDM_CompanionTransportManager";
+ private static final boolean DEBUG = false;
private boolean mSecureTransportEnabled = true;
@@ -136,17 +137,11 @@ public class CompanionTransportManager {
}
}
- /**
- * Attach transport.
- */
- public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
- Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
-
- mAssociationStore.getAssociationWithCallerChecks(associationId);
-
+ public void attachSystemDataTransport(String packageName, int userId, int associationId,
+ ParcelFileDescriptor fd) {
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
- detachSystemDataTransport(associationId);
+ detachSystemDataTransport(packageName, userId, associationId);
}
// TODO: Implement new API to pass a PSK
@@ -154,18 +149,9 @@ public class CompanionTransportManager {
notifyOnTransportsChanged();
}
-
- Slog.i(TAG, "Transport attached.");
}
- /**
- * Detach transport.
- */
- public void detachSystemDataTransport(int associationId) {
- Slog.i(TAG, "Detaching transport for association id=[" + associationId + "]...");
-
- mAssociationStore.getAssociationWithCallerChecks(associationId);
-
+ public void detachSystemDataTransport(String packageName, int userId, int associationId) {
synchronized (mTransports) {
final Transport transport = mTransports.removeReturnOld(associationId);
if (transport == null) {
@@ -175,8 +161,6 @@ public class CompanionTransportManager {
transport.stop();
notifyOnTransportsChanged();
}
-
- Slog.i(TAG, "Transport detached.");
}
private void notifyOnTransportsChanged() {
@@ -323,7 +307,8 @@ public class CompanionTransportManager {
int associationId = transport.mAssociationId;
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
if (association != null) {
- detachSystemDataTransport(
+ detachSystemDataTransport(association.getPackageName(),
+ association.getUserId(),
association.getId());
}
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index d7e766eed209..2cf1f462a7d1 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -39,6 +39,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
@@ -207,7 +208,7 @@ public final class PermissionsUtils {
/**
* Require the caller to hold necessary permission to observe device presence by UUID.
*/
- public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
+ public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
@@ -234,6 +235,23 @@ public final class PermissionsUtils {
return checkCallerCanManageCompanionDevice(context);
}
+ /**
+ * Check if CDM can trust the context to process the association.
+ */
+ @Nullable
+ public static AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
+ @Nullable AssociationInfo association) {
+ if (association == null) return null;
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
+ return null;
+ }
+
+ return association;
+ }
+
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 589d8b373802..e3f16ae07202 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -43,10 +43,12 @@ import android.view.ISensitiveContentProtectionManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import com.android.server.wm.WindowManagerInternal;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
/**
@@ -61,8 +63,15 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
@VisibleForTesting
@Nullable
NotificationListener mNotificationListener;
- @Nullable private MediaProjectionManager mProjectionManager;
- @Nullable private WindowManagerInternal mWindowManager;
+ @Nullable
+ private MediaProjectionManager mProjectionManager;
+ @Nullable
+ private MediaProjectionSession mMediaProjectionSession;
+
+ private PackageManagerInternal mPackageManagerInternal;
+
+ @Nullable
+ private WindowManagerInternal mWindowManager;
// screen recorder packages exempted from screen share protection.
private ArraySet<String> mExemptedPackages = null;
@@ -74,6 +83,16 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
@GuardedBy("mSensitiveContentProtectionLock")
private boolean mProjectionActive = false;
+ private static class MediaProjectionSession {
+ final int mUid;
+ final long mSessionId;
+
+ MediaProjectionSession(int uid, long sessionId) {
+ mUid = uid;
+ mSessionId = sessionId;
+ }
+ }
+
private final MediaProjectionManager.Callback mProjectionCallback =
new MediaProjectionManager.Callback() {
@Override
@@ -82,7 +101,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
- onProjectionStart(info.getPackageName());
+ onProjectionStart(info);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
@@ -119,12 +138,13 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED");
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
init(getContext().getSystemService(MediaProjectionManager.class),
LocalServices.getService(WindowManagerInternal.class),
getExemptedPackages());
if (sensitiveContentAppProtection()) {
publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE,
- new SensitiveContentProtectionManagerServiceBinder());
+ new SensitiveContentProtectionManagerServiceBinder(mPackageManagerInternal));
}
}
@@ -186,11 +206,12 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
return SystemConfig.getInstance().getBugreportWhitelistedPackages();
}
- private void onProjectionStart(String packageName) {
+ private void onProjectionStart(MediaProjectionInfo projectionInfo) {
// exempt on device screen recorder as well.
- if ((mExemptedPackages != null && mExemptedPackages.contains(packageName))
- || canRecordSensitiveContent(packageName)) {
- Log.w(TAG, packageName + " is exempted from screen share protection.");
+ if ((mExemptedPackages != null && mExemptedPackages.contains(
+ projectionInfo.getPackageName()))
+ || canRecordSensitiveContent(projectionInfo.getPackageName())) {
+ Log.w(TAG, projectionInfo.getPackageName() + " is exempted.");
return;
}
// TODO(b/324447419): move GlobalSettings lookup to background thread
@@ -204,6 +225,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = true;
+ int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
+ projectionInfo.getUserHandle().getIdentifier());
+ // TODO review sessionId, whether to use a sequence generator or random is good?
+ mMediaProjectionSession = new MediaProjectionSession(uid, new Random().nextLong());
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
}
@@ -217,6 +242,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
private void onProjectionEnd() {
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = false;
+ mMediaProjectionSession = null;
// notify windowmanager to clear any sensitive notifications observed during projection
// session
@@ -351,9 +377,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
* Block projection for a package window when the window is showing sensitive content on
* the screen, the projection is unblocked when window no more shows sensitive content.
*
- * @param windowToken window where the content is shown.
- * @param packageName package name.
- * @param uid uid of the package.
+ * @param windowToken window where the content is shown.
+ * @param packageName package name.
+ * @param uid uid of the package.
* @param isShowingSensitiveContent whether the window is showing sensitive content.
*/
@VisibleForTesting
@@ -385,8 +411,22 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
packageInfos.add(packageInfo);
if (isShowingSensitiveContent) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mMediaProjectionSession.mSessionId,
+ uid,
+ mMediaProjectionSession.mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
+ );
} else {
mWindowManager.removeBlockScreenCaptureForApps(packageInfos);
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mMediaProjectionSession.mSessionId,
+ uid,
+ mMediaProjectionSession.mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
+ );
}
}
}
@@ -395,8 +435,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
extends ISensitiveContentProtectionManager.Stub {
private final PackageManagerInternal mPackageManagerInternal;
- SensitiveContentProtectionManagerServiceBinder() {
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ SensitiveContentProtectionManagerServiceBinder(
+ PackageManagerInternal packageManagerInternal) {
+ mPackageManagerInternal = packageManagerInternal;
}
public void setSensitiveContentProtection(IBinder windowToken, String packageName,
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index b8ef03f36c23..f4a931f89551 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -289,27 +289,22 @@ public class BroadcastSkipPolicy {
if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource attributionSource;
+ final AttributionSource[] attributionSources;
if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSource =
- new AttributionSource.Builder(info.activityInfo.applicationInfo.uid)
- .setPackageName(info.activityInfo.packageName)
- .build();
+ attributionSources = createAttributionSourcesForResolveInfo(info);
} else {
- attributionSource = null;
+ attributionSources = null;
}
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
try {
if (usePermissionManagerForBroadcastDeliveryCheck()) {
- final PermissionManager permissionManager = getPermissionManager();
- if (permissionManager != null) {
- perm = permissionManager.checkPermissionForDataDelivery(
- requiredPermission, attributionSource, null /* message */);
- } else {
- // Assume permission denial if PermissionManager is not yet available.
- perm = PackageManager.PERMISSION_DENIED;
- }
+ perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to " + info.activityInfo.name,
+ attributionSources)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
} else {
perm = AppGlobals.getPackageManager()
.checkPermission(
@@ -457,10 +452,35 @@ public class BroadcastSkipPolicy {
// Check that the receiver has the required permission(s) to receive this broadcast.
if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
+ final AttributionSource attributionSource;
+ if (usePermissionManagerForBroadcastDeliveryCheck()) {
+ attributionSource =
+ new AttributionSource.Builder(filter.receiverList.uid)
+ .setPid(filter.receiverList.pid)
+ .setPackageName(filter.packageName)
+ .setAttributionTag(filter.featureId)
+ .build();
+ } else {
+ attributionSource = null;
+ }
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- int perm = checkComponentPermission(requiredPermission,
- filter.receiverList.pid, filter.receiverList.uid, -1, true);
+ final int perm;
+ if (usePermissionManagerForBroadcastDeliveryCheck()) {
+ perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to registered receiver " + filter.receiverId,
+ attributionSource)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
+ } else {
+ perm = checkComponentPermission(
+ requiredPermission,
+ filter.receiverList.pid,
+ filter.receiverList.uid,
+ -1 /* owningUid */,
+ true /* exported */);
+ }
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent.toString()
@@ -471,21 +491,23 @@ public class BroadcastSkipPolicy {
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
- && mService.getAppOpsManager().noteOpNoThrow(appOp,
- filter.receiverList.uid, filter.packageName, filter.featureId,
- "Broadcast delivered to registered receiver " + filter.receiverId)
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: receiving "
- + r.intent.toString()
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")"
- + " requires appop " + AppOpsManager.permissionToOp(
- requiredPermission)
- + " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")";
+ if (!usePermissionManagerForBroadcastDeliveryCheck()) {
+ int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
+ if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
+ && mService.getAppOpsManager().noteOpNoThrow(appOp,
+ filter.receiverList.uid, filter.packageName, filter.featureId,
+ "Broadcast delivered to registered receiver " + filter.receiverId)
+ != AppOpsManager.MODE_ALLOWED) {
+ return "Appop Denial: receiving "
+ + r.intent.toString()
+ + " to " + filter.receiverList.app
+ + " (pid=" + filter.receiverList.pid
+ + ", uid=" + filter.receiverList.uid + ")"
+ + " requires appop " + AppOpsManager.permissionToOp(
+ requiredPermission)
+ + " due to sender " + r.callerPackage
+ + " (uid " + r.callingUid + ")";
+ }
}
}
}
@@ -724,10 +746,54 @@ public class BroadcastSkipPolicy {
return false;
}
+ @Nullable
private PermissionManager getPermissionManager() {
if (mPermissionManager == null) {
mPermissionManager = mService.mContext.getSystemService(PermissionManager.class);
}
return mPermissionManager;
}
+
+ private boolean hasPermissionForDataDelivery(
+ @NonNull String permission,
+ @NonNull String message,
+ @NonNull AttributionSource... attributionSources) {
+ final PermissionManager permissionManager = getPermissionManager();
+ if (permissionManager == null) {
+ return false;
+ }
+
+ for (AttributionSource attributionSource : attributionSources) {
+ final int permissionCheckResult =
+ permissionManager.checkPermissionForDataDelivery(
+ permission, attributionSource, message);
+ if (permissionCheckResult != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private AttributionSource[] createAttributionSourcesForResolveInfo(ResolveInfo info) {
+ final String[] attributionTags = info.activityInfo.attributionTags;
+ if (ArrayUtils.isEmpty(attributionTags)) {
+ return new AttributionSource[] {
+ new AttributionSource.Builder(info.activityInfo.applicationInfo.uid)
+ .setPackageName(info.activityInfo.packageName)
+ .build()
+ };
+ }
+
+ final AttributionSource[] attributionSources =
+ new AttributionSource[attributionTags.length];
+ for (int i = 0; i < attributionTags.length; i++) {
+ attributionSources[i] =
+ new AttributionSource.Builder(info.activityInfo.applicationInfo.uid)
+ .setPackageName(info.activityInfo.packageName)
+ .setAttributionTag(attributionTags[i])
+ .build();
+ }
+ return attributionSources;
+ }
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 48d3c09290ce..390dca30ac96 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -125,13 +125,13 @@ public class SettingsToPropertiesMapper {
"app_widgets",
"arc_next",
"avic",
+ "biometrics",
+ "biometrics_framework",
+ "biometrics_integration",
"bluetooth",
"brownout_mitigation_audio",
"brownout_mitigation_modem",
"build",
- "biometrics",
- "biometrics_framework",
- "biometrics_integration",
"camera_hal",
"camera_platform",
"car_framework",
@@ -160,8 +160,8 @@ public class SettingsToPropertiesMapper {
"media_audio",
"media_drm",
"media_reliability",
- "media_tv",
"media_solutions",
+ "media_tv",
"nfc",
"pdf_viewer",
"perfetto",
@@ -183,18 +183,20 @@ public class SettingsToPropertiesMapper {
"rust",
"safety_center",
"sensors",
+ "spoon",
+ "statsd",
"system_performance",
"system_sw_touch",
"system_sw_usb",
- "statsd",
"test_suites",
"text",
"threadnetwork",
+ "treble",
"tv_system_ui",
"usb",
"vibrator",
- "virtualization",
"virtual_devices",
+ "virtualization",
"wallet_integration",
"wear_calling_messaging",
"wear_connectivity",
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b7e11a7fe261..14f3120b60b6 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -312,8 +312,8 @@ public class AuthService extends SystemService {
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
}
- if (promptInfo.containsSetLogoApiConfigurations()) {
- checkManageBiometricPermission();
+ if (promptInfo.containsAdvancedApiConfigurations()) {
+ checkBiometricAdvancedPermission();
}
final long identity = Binder.clearCallingIdentity();
@@ -1029,7 +1029,7 @@ public class AuthService extends SystemService {
"Must have USE_BIOMETRIC_INTERNAL permission");
}
- private void checkManageBiometricPermission() {
+ private void checkBiometricAdvancedPermission() {
getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_ADVANCED,
"Must have SET_BIOMETRIC_DIALOG_ADVANCED permission");
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 0ebb2a30685c..09094ffb4f0d 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayAddress;
@@ -35,6 +36,7 @@ import android.view.SurfaceControl;
import com.android.server.display.mode.DisplayModeDirector;
import java.io.PrintWriter;
+import java.util.Arrays;
/**
* Represents a display device such as the built-in display, an external monitor, a WiFi display,
@@ -429,6 +431,21 @@ abstract class DisplayDevice {
return mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270;
}
+ /**
+ * @return set of supported resolutions as an ascending sorted array.
+ */
+ Point[] getSupportedResolutionsLocked() {
+ ArraySet<Point> resolutions = new ArraySet<>(2);
+ Display.Mode[] supportedModes = getDisplayDeviceInfoLocked().supportedModes;
+ for (Display.Mode mode : supportedModes) {
+ resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
+ }
+ Point[] sortedArray = new Point[resolutions.size()];
+ resolutions.toArray(sortedArray);
+ Arrays.sort(sortedArray, (p1, p2) -> p1.x * p1.y - p2.x * p2.y);
+ return sortedArray;
+ }
+
private DisplayDeviceConfig loadDisplayDeviceConfig() {
return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
mDisplayAdapter.getFeatureFlags());
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index da26209ad495..641b6a2f170b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -54,6 +54,7 @@ import com.android.server.display.config.DisplayBrightnessMappingConfig;
import com.android.server.display.config.DisplayBrightnessPoint;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
+import com.android.server.display.config.EvenDimmerBrightnessData;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.HighBrightnessMode;
@@ -61,7 +62,6 @@ import com.android.server.display.config.IdleScreenRefreshRateTimeout;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
import com.android.server.display.config.IntegerArray;
-import com.android.server.display.config.LowBrightnessData;
import com.android.server.display.config.LuxThrottling;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.NonNegativeFloatToFloatPoint;
@@ -556,7 +556,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <majorVersion>2</majorVersion>
* <minorVersion>0</minorVersion>
* </usiVersion>
- * <lowBrightness enabled="true">
+ * <evenDimmer enabled="true">
* <transitionPoint>0.1</transitionPoint>
*
* <nits>0.2</nits>
@@ -573,7 +573,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <brightness>0.1</brightness>
* <brightness>0.5</brightness>
* <brightness>1.0</brightness>
- * </lowBrightness>
+ * </evenDimmer>
* <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
* <idleScreenRefreshRateTimeout>
* <luxThresholds>
@@ -894,9 +894,9 @@ public class DisplayDeviceConfig {
@Nullable
private HdrBrightnessData mHdrBrightnessData;
- // Null if low brightness mode is disabled - in config or by flag.
+ // Null if even dimmer is disabled - in config or by flag.
@Nullable
- public LowBrightnessData mLowBrightnessData;
+ public EvenDimmerBrightnessData mEvenDimmerBrightnessData;
/**
* Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
@@ -1064,8 +1064,8 @@ public class DisplayDeviceConfig {
* @return The brightness mapping nits array.
*/
public float[] getNits() {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mNits;
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mNits;
}
return mNits;
}
@@ -1077,8 +1077,8 @@ public class DisplayDeviceConfig {
*/
@VisibleForTesting
public float[] getBacklight() {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mBacklight;
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mBacklight;
}
return mBacklight;
}
@@ -1091,8 +1091,8 @@ public class DisplayDeviceConfig {
* @return backlight value on the HAL scale of 0-1
*/
public float getBacklightFromBrightness(float brightness) {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mBrightnessToBacklight.interpolate(brightness);
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mBrightnessToBacklight.interpolate(brightness);
}
return mBrightnessToBacklightSpline.interpolate(brightness);
}
@@ -1104,30 +1104,19 @@ public class DisplayDeviceConfig {
* @return brightness value from 0-1 framework scale
*/
public float getBrightnessFromBacklight(float backlight) {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mBacklightToBrightness.interpolate(backlight);
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mBacklightToBrightness.interpolate(backlight);
}
return mBacklightToBrightnessSpline.interpolate(backlight);
}
/**
*
- * @return low brightness mode transition point
- */
- public float getLowBrightnessTransitionPoint() {
- if (mLowBrightnessData == null) {
- return PowerManager.BRIGHTNESS_MIN;
- }
- return mLowBrightnessData.mTransitionPoint;
- }
-
- /**
- *
* @return HAL backlight mapping to framework brightness
*/
private Spline getBacklightToBrightnessSpline() {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mBacklightToBrightness;
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mBacklightToBrightness;
}
return mBacklightToBrightnessSpline;
}
@@ -1139,12 +1128,12 @@ public class DisplayDeviceConfig {
* exits.
*/
public float getNitsFromBacklight(float backlight) {
- if (mLowBrightnessData != null) {
- if (mLowBrightnessData.mBacklightToNits == null) {
+ if (mEvenDimmerBrightnessData != null) {
+ if (mEvenDimmerBrightnessData.mBacklightToNits == null) {
return INVALID_NITS;
}
backlight = Math.max(backlight, mBacklightMinimum);
- return mLowBrightnessData.mBacklightToNits.interpolate(backlight);
+ return mEvenDimmerBrightnessData.mBacklightToNits.interpolate(backlight);
}
if (mBacklightToNitsSpline == null) {
@@ -1160,15 +1149,15 @@ public class DisplayDeviceConfig {
* @return corresponding HAL backlight value
*/
public float getBacklightFromNits(float nits) {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mNitsToBacklight.interpolate(nits);
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mNitsToBacklight.interpolate(nits);
}
return mNitsToBacklightSpline.interpolate(nits);
}
private Spline getNitsToBacklightSpline() {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mNitsToBacklight;
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mNitsToBacklight;
}
return mNitsToBacklightSpline;
}
@@ -1179,10 +1168,21 @@ public class DisplayDeviceConfig {
* @return minimum allowed nits, given the lux.
*/
public float getMinNitsFromLux(float lux) {
- if (mLowBrightnessData == null) {
+ if (mEvenDimmerBrightnessData == null) {
return INVALID_NITS;
}
- return mLowBrightnessData.mMinLuxToNits.interpolate(lux);
+ return mEvenDimmerBrightnessData.mMinLuxToNits.interpolate(lux);
+ }
+
+ /**
+ *
+ * @return even dimmer mode transition point
+ */
+ public float getEvenDimmerTransitionPoint() {
+ if (mEvenDimmerBrightnessData == null) {
+ return PowerManager.BRIGHTNESS_MIN;
+ }
+ return mEvenDimmerBrightnessData.mTransitionPoint;
}
/**
@@ -1239,8 +1239,8 @@ public class DisplayDeviceConfig {
* @return brightness array
*/
public float[] getBrightness() {
- if (mLowBrightnessData != null) {
- return mLowBrightnessData.mBrightness;
+ if (mEvenDimmerBrightnessData != null) {
+ return mEvenDimmerBrightnessData.mBrightness;
}
return mBrightness;
}
@@ -1928,11 +1928,11 @@ public class DisplayDeviceConfig {
/**
*
- * @return true if low brightness mode is enabled
+ * @return true if even dimmer mode is enabled
*/
@VisibleForTesting
- public boolean getLbmEnabled() {
- return mLowBrightnessData != null;
+ public boolean isEvenDimmerAvailable() {
+ return mEvenDimmerBrightnessData != null;
}
/**
@@ -2075,8 +2075,8 @@ public class DisplayDeviceConfig {
+ "mHdrBrightnessData= " + mHdrBrightnessData + "\n"
+ "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode
+ "\n"
- + "mLowBrightnessData:" + (mLowBrightnessData != null
- ? mLowBrightnessData.toString() : "null")
+ + "mEvenDimmerBrightnessData:" + (mEvenDimmerBrightnessData != null
+ ? mEvenDimmerBrightnessData.toString() : "null")
+ "}";
}
@@ -2128,7 +2128,7 @@ public class DisplayDeviceConfig {
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
if (mFlags.isEvenDimmerEnabled()) {
- mLowBrightnessData = LowBrightnessData.loadConfig(config);
+ mEvenDimmerBrightnessData = EvenDimmerBrightnessData.loadConfig(config);
}
loadBrightnessMap(config);
loadThermalThrottlingConfig(config);
@@ -2923,7 +2923,7 @@ public class DisplayDeviceConfig {
private void createBacklightConversionSplines() {
- // Create original brightness splines - not using low brightness mode arrays - this is
+ // Create original brightness splines - not using even dimmer mode arrays - this is
// so that we can continue to log the original brightness splines.
mBrightness = new float[mBacklight.length];
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 84eebe838954..ba21a327d837 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -45,6 +45,9 @@ import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPOR
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
+import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
+import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
+import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
import android.Manifest;
import android.annotation.EnforcePermission;
@@ -531,6 +534,18 @@ public final class DisplayManagerService extends SystemService {
}
};
+ private final BroadcastReceiver mResolutionRestoreReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
+ if (Settings.Secure.SCREEN_RESOLUTION_MODE.equals(
+ intent.getExtra(Intent.EXTRA_SETTING_NAME))) {
+ restoreResolutionFromBackup();
+ }
+ }
+ }
+ };
+
private final BrightnessSynchronizer mBrightnessSynchronizer;
private final DeviceConfigParameterProvider mConfigParameterProvider;
@@ -560,6 +575,9 @@ public final class DisplayManagerService extends SystemService {
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE = 170503758L;
+ private final Uri mScreenResolutionModeUri = Settings.Secure.getUriFor(
+ Settings.Secure.SCREEN_RESOLUTION_MODE);
+
public DisplayManagerService(Context context) {
this(context, new Injector());
}
@@ -782,6 +800,11 @@ public final class DisplayManagerService extends SystemService {
mContext.registerReceiver(mIdleModeReceiver, filter);
+ if (mFlags.isResolutionBackupRestoreEnabled()) {
+ final IntentFilter restoreFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+ mContext.registerReceiver(mResolutionRestoreReceiver, restoreFilter);
+ }
+
mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
? SmallAreaDetectionController.create(mContext) : null;
}
@@ -1031,6 +1054,44 @@ public final class DisplayManagerService extends SystemService {
1, UserHandle.USER_CURRENT) != 0);
}
+ private void restoreResolutionFromBackup() {
+ int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREEN_RESOLUTION_MODE,
+ RESOLUTION_MODE_UNKNOWN, UserHandle.USER_CURRENT);
+ if (savedMode == RESOLUTION_MODE_UNKNOWN) {
+ // Nothing to restore.
+ return;
+ }
+
+ synchronized (mSyncRoot) {
+ LogicalDisplay display =
+ mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY);
+ DisplayDevice device = display == null ? null : display.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ Slog.w(TAG, "No default display device present to restore resolution mode");
+ return;
+ }
+
+ Point[] supportedRes = device.getSupportedResolutionsLocked();
+ if (supportedRes.length != 2) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping resolution restore - " + supportedRes.length);
+ }
+ return;
+ }
+
+ // We follow the same logic as Settings but in reverse. If the display supports 2
+ // resolutions, we treat the small (index=0) one as HIGH and the larger (index=1)
+ // one as FULL and restore the correct resolution accordingly.
+ int index = savedMode == RESOLUTION_MODE_HIGH ? 0 : 1;
+ Point res = supportedRes[index];
+ Display.Mode newMode = new Display.Mode(res.x, res.y, /*refreshRate=*/ 0);
+ Slog.i(TAG, "Restoring resolution from backup: (" + savedMode + ") "
+ + res.x + "x" + res.y);
+ setUserPreferredDisplayModeInternal(Display.DEFAULT_DISPLAY, newMode);
+ }
+ }
+
private void updateUserDisabledHdrTypesFromSettingsLocked() {
mAreUserDisabledHdrTypesAllowed = (Settings.Global.getInt(
mContext.getContentResolver(),
@@ -2348,6 +2409,28 @@ public final class DisplayManagerService extends SystemService {
if (displayDevice == null) {
return;
}
+
+ // We do not yet support backup and restore for our PersistentDataStore, however, we want to
+ // preserve the user's choice for HIGH/FULL resolution setting, so we when we are given a
+ // a new resolution for the default display (normally stored in PDS), we will also save it
+ // to a setting that is backed up.
+ // TODO(b/330943343) - Consider a full fidelity DisplayBackupHelper for this instead.
+ if (mFlags.isResolutionBackupRestoreEnabled() && displayId == Display.DEFAULT_DISPLAY) {
+ // Checks to see which of the two resolutions is selected
+ // TODO(b/330906790) Uses the same logic as Settings, but should be made to support
+ // more than two resolutions using explicit mode enums long-term.
+ Point[] resolutions = displayDevice.getSupportedResolutionsLocked();
+ if (resolutions.length == 2) {
+ Point newMode = new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight());
+ int resolutionMode = newMode.equals(resolutions[0]) ? RESOLUTION_MODE_HIGH
+ : newMode.equals(resolutions[1]) ? RESOLUTION_MODE_FULL
+ : RESOLUTION_MODE_UNKNOWN;
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREEN_RESOLUTION_MODE, resolutionMode,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
displayDevice.setUserPreferredDisplayModeLocked(mode);
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 86fab17e6ae8..a577e225076f 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -53,6 +53,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.notifications.DisplayNotificationManager;
@@ -100,6 +101,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private Context mOverlayContext;
private int mEvenDimmerStrength = -1;
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsi;
// Called with SyncRoot lock held.
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
@@ -1000,9 +1002,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
|| strength <= 1) {
mEvenDimmerStrength = strength;
}
+ boolean enabled = mEvenDimmerStrength > 0.0f;
- // TODO: use `enabled` and `mRbcStrength` to set color matrices here
- // TODO: boolean enabled = mEvenDimmerStrength > 0.0f;
+ if (mCdsi == null) {
+ mCdsi = LocalServices.getService(
+ ColorDisplayService.ColorDisplayServiceInternal.class);
+ }
+ if (mCdsi != null) {
+ mCdsi.applyEvenDimmerColorChanges(enabled, strength);
+ }
}
};
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index 7f88c3029820..29b457fca8fc 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -106,7 +106,7 @@ public class BrightnessLowLuxModifier extends BrightnessModifier {
? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
: BrightnessReason.MODIFIER_MIN_LUX;
} else {
- minBrightnessAllowed = mDisplayDeviceConfig.getLowBrightnessTransitionPoint();
+ minBrightnessAllowed = mDisplayDeviceConfig.getEvenDimmerTransitionPoint();
reason = 0;
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index a313bcf1f7af..0bb93a96d9df 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1632,6 +1632,15 @@ public final class ColorDisplayService extends SystemService {
return mAppSaturationController
.addColorTransformController(packageName, userId, controller);
}
+
+ /**
+ * Applies tint changes for even dimmer feature.
+ */
+ public void applyEvenDimmerColorChanges(boolean enabled, int strength) {
+ mReduceBrightColorsTintController.setActivated(enabled);
+ mReduceBrightColorsTintController.setMatrix(strength);
+ mHandler.sendEmptyMessage(MSG_APPLY_REDUCE_BRIGHT_COLORS);
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/config/LowBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 1a4e807fece6..555636538893 100644
--- a/services/core/java/com/android/server/display/config/LowBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -26,13 +26,13 @@ import java.util.Arrays;
import java.util.List;
/**
- * Brightness config for low brightness mode
+ * Brightness config for even dimmer
*/
-public class LowBrightnessData {
- private static final String TAG = "LowBrightnessData";
+public class EvenDimmerBrightnessData {
+ private static final String TAG = "EvenDimmerBrightnessData";
/**
- * Brightness value at which lower brightness methods are used.
+ * Brightness value at which even dimmer methods are used.
*/
public final float mTransitionPoint;
@@ -69,7 +69,7 @@ public class LowBrightnessData {
public final Spline mMinLuxToNits;
@VisibleForTesting
- public LowBrightnessData(float transitionPoint, float[] nits,
+ public EvenDimmerBrightnessData(float transitionPoint, float[] nits,
float[] backlight, float[] brightness, Spline backlightToNits,
Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness,
Spline minLuxToNits) {
@@ -86,7 +86,7 @@ public class LowBrightnessData {
@Override
public String toString() {
- return "LowBrightnessData {"
+ return "EvenDimmerBrightnessData {"
+ "mTransitionPoint: " + mTransitionPoint
+ ", mNits: " + Arrays.toString(mNits)
+ ", mBacklight: " + Arrays.toString(mBacklight)
@@ -100,11 +100,11 @@ public class LowBrightnessData {
}
/**
- * Loads LowBrightnessData from DisplayConfiguration
+ * Loads EvenDimmerBrightnessData from DisplayConfiguration
*/
@Nullable
- public static LowBrightnessData loadConfig(DisplayConfiguration config) {
- final LowBrightnessMode lbm = config.getLowBrightness();
+ public static EvenDimmerBrightnessData loadConfig(DisplayConfiguration config) {
+ final EvenDimmerMode lbm = config.getEvenDimmer();
if (lbm == null) {
return null;
}
@@ -122,7 +122,7 @@ public class LowBrightnessData {
if (nitsList.isEmpty()
|| backlightList.size() != brightnessList.size()
|| backlightList.size() != nitsList.size()) {
- Slog.e(TAG, "Invalid low brightness array lengths");
+ Slog.e(TAG, "Invalid even dimmer array lengths");
return null;
}
@@ -164,7 +164,7 @@ public class LowBrightnessData {
++i;
}
- return new LowBrightnessData(transitionPoints, nits, backlight, brightness,
+ return new EvenDimmerBrightnessData(transitionPoints, nits, backlight, brightness,
Spline.createSpline(backlight, nits),
Spline.createSpline(nits, backlight),
Spline.createSpline(brightness, backlight),
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index e1a166ec95f5..81f824e69cf3 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -96,6 +96,10 @@ public class DisplayManagerFlags {
Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES,
Flags::enableRestrictDisplayModes);
+ private final FlagState mResolutionBackupRestore = new FlagState(
+ Flags.FLAG_RESOLUTION_BACKUP_RESTORE,
+ Flags::resolutionBackupRestore);
+
private final FlagState mVsyncLowPowerVote = new FlagState(
Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE,
Flags::enableVsyncLowPowerVote);
@@ -246,6 +250,10 @@ public class DisplayManagerFlags {
return mRestrictDisplayModes.isEnabled();
}
+ public boolean isResolutionBackupRestoreEnabled() {
+ return mResolutionBackupRestore.isEnabled();
+ }
+
public boolean isVsyncLowPowerVoteEnabled() {
return mVsyncLowPowerVote.isEnabled();
}
@@ -309,6 +317,7 @@ public class DisplayManagerFlags {
pw.println(" " + mHdrClamperFlagState);
pw.println(" " + mNbmControllerFlagState);
pw.println(" " + mPowerThrottlingClamperFlagState);
+ pw.println(" " + mEvenDimmerFlagState);
pw.println(" " + mSmallAreaDetectionFlagState);
pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
pw.println(" " + mRestrictDisplayModes);
@@ -321,6 +330,7 @@ public class DisplayManagerFlags {
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mIdleScreenRefreshRateTimeout);
pw.println(" " + mRefactorDisplayPowerController);
+ pw.println(" " + mResolutionBackupRestore);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 7e190ddbb24f..4e9cf51fda56 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -141,6 +141,11 @@ public abstract class InputManagerInternal {
*/
public abstract void unregisterLidSwitchCallback(@NonNull LidSwitchCallback callbacks);
+ /**
+ * Notify the input manager that an IME connection is becoming active or is no longer active.
+ */
+ public abstract void notifyInputMethodConnectionActive(boolean connectionIsActive);
+
/** 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 468b90259fc7..77119d5ac384 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2846,13 +2846,6 @@ public class InputManagerService extends IInputManager.Stub
lockedModifierState);
}
- // Native callback.
- @SuppressWarnings("unused")
- boolean isInputMethodConnectionActive() {
- return mInputMethodManagerInternal != null
- && mInputMethodManagerInternal.isAnyInputConnectionActive();
- }
-
/**
* Callback interface implemented by the Window Manager.
*/
@@ -3301,6 +3294,11 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
+ public void notifyInputMethodConnectionActive(boolean connectionIsActive) {
+ mNative.setInputMethodConnectionIsActive(connectionIsActive);
+ }
+
+ @Override
public InputChannel createInputChannel(String inputChannelName) {
return InputManagerService.this.createInputChannel(inputChannelName);
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 972a9e34f496..32d5044d9e41 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -273,6 +273,8 @@ interface NativeInputManagerService {
*/
void setAccessibilityStickyKeysEnabled(boolean enabled);
+ void setInputMethodConnectionIsActive(boolean isActive);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -549,5 +551,8 @@ interface NativeInputManagerService {
@Override
public native void setAccessibilityStickyKeysEnabled(boolean enabled);
+
+ @Override
+ public native void setInputMethodConnectionIsActive(boolean isActive);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index dda50cab2cdd..1d048cb687cb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -235,12 +235,6 @@ public abstract class InputMethodManagerInternal {
IBinder targetWindowToken);
/**
- * Returns true if any InputConnection is currently active.
- * {@hide}
- */
- public abstract boolean isAnyInputConnectionActive();
-
- /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
private static final InputMethodManagerInternal NOP =
@@ -331,11 +325,6 @@ public abstract class InputMethodManagerInternal {
public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
IBinder targetWindowToken) {
}
-
- @Override
- public boolean isAnyInputConnectionActive() {
- return false;
- }
};
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b595f0e71d16..c6a48ec9018e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2177,6 +2177,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
prepareClientSwitchLocked(cs);
}
+ final boolean connectionWasActive = mCurInputConnection != null;
+
// Bump up the sequence for this client and attach it.
advanceSequenceNumberLocked();
mCurClient = cs;
@@ -2195,6 +2197,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
mCurEditorInfo = editorInfo;
+ // Notify input manager if the connection state changes.
+ final boolean connectionIsActive = mCurInputConnection != null;
+ if (connectionIsActive != connectionWasActive) {
+ mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
+ }
+
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
unverifiedTargetSdkVersion)) {
@@ -5693,14 +5701,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
switchKeyboardLayoutLocked(direction);
}
}
-
- /**
- * Returns true if any InputConnection is currently active.
- */
- @Override
- public boolean isAnyInputConnectionActive() {
- return mCurInputConnection != null;
- }
}
@BinderThread
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 319947ab1016..67bc61c3b9f6 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -336,6 +336,14 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
}
}
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ RoutingSessionInfo oldSessionInfo = mSessionInfos.get(0);
+ builder.setTransferReason(oldSessionInfo.getTransferReason())
+ .setTransferInitiator(oldSessionInfo.getTransferInitiatorUserHandle(),
+ oldSessionInfo.getTransferInitiatorPackageName());
+ }
+
return builder.setProviderId(mUniqueId).build();
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3f041cb48ee2..1da9f25afbc7 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1723,7 +1723,7 @@ final class AccessibilityController {
mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
mDisplayId, visibleWindows);
- if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) {
+ if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
windows = buildWindowInfoListLocked(visibleWindows, screenSize);
}
@@ -1732,7 +1732,7 @@ final class AccessibilityController {
topFocusedWindowToken = topFocusedWindowState.mClient.asBinder();
}
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) {
+ if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
topFocusedWindowToken, screenSize, visibleWindows);
} else {
@@ -1747,7 +1747,7 @@ final class AccessibilityController {
mInitialized = true;
}
- // Here are old code paths, called when computeWindowChangesOnA11y flag is disabled.
+ // Here are old code paths, called when computeWindowChangesOnA11yV2 flag is disabled.
// LINT.IfChange
/**
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index ac3251c9bb12..f6afc52fd8d8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -722,7 +722,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
}
// Compute system bar insets frame if needed.
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()
+ if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()
&& windowState != null && instance.isUntouchableNavigationBar()) {
final InsetsSourceProvider provider =
windowState.getControllableInsetProvider();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 4ec2f576efa9..07f52574395c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2266,7 +2266,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
* activity releases the top state and reports back, message about acquiring top state will be
* sent to the new top resumed activity.
*/
- void updateTopResumedActivityIfNeeded(String reason) {
+ ActivityRecord updateTopResumedActivityIfNeeded(String reason) {
final ActivityRecord prevTopActivity = mTopResumedActivity;
final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
@@ -2279,7 +2279,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// according to the current top focused activity.
mService.updateTopApp(null /* topResumedActivity */);
}
- return;
+ return mTopResumedActivity;
}
// Ask previous activity to release the top state.
@@ -2306,6 +2306,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
scheduleTopResumedActivityStateIfNeeded();
mService.updateTopApp(mTopResumedActivity);
+
+ return mTopResumedActivity;
}
/** Schedule current top resumed activity state loss */
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 2788342cb097..933633836e56 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -853,8 +853,7 @@ public class BackgroundActivityStartController {
}
// don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
- if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
- callingPid, callingUid) == PERMISSION_GRANTED) {
+ if (hasBalPermission(callingUid, callingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
/*background*/ true,
"START_ACTIVITIES_FROM_BACKGROUND permission granted");
@@ -919,9 +918,7 @@ public class BackgroundActivityStartController {
BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
if (state.isPendingIntentBalAllowedByPermission()
- && ActivityManager.checkComponentPermission(
- android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
- state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
+ && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
/*background*/ false,
"realCallingUid has BAL permission.");
@@ -980,6 +977,11 @@ public class BackgroundActivityStartController {
return BalVerdict.BLOCK;
}
+ @VisibleForTesting boolean hasBalPermission(int uid, int pid) {
+ return ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
+ pid, uid) == PERMISSION_GRANTED;
+ }
+
/**
* Check if the app allows BAL.
* <p>
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9b2ca3953b0d..b89c12bca62f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1074,9 +1074,11 @@ class Task extends TaskFragment {
// as the one in the task because either one of them could be the alias activity.
if (Objects.equals(realActivity, r.mActivityComponent) && this.intent != null) {
intent.setComponent(this.intent.getComponent());
- // Make sure the package name the same to prevent one of the intent is set while the
- // other one is not.
- intent.setPackage(this.intent.getPackage());
+ if (intent.getSelector() == null) {
+ // Make sure the package name the same to prevent one of the intent is set while the
+ // other one is not.
+ intent.setPackage(this.intent.getPackage());
+ }
}
return intent.filterEquals(this.intent);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index dc0e0341ee8b..bd1503f15666 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -579,7 +579,13 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final ActivityRecord prevR = mResumedActivity;
mResumedActivity = r;
- mTaskSupervisor.updateTopResumedActivityIfNeeded(reason);
+ final ActivityRecord topResumed = mTaskSupervisor.updateTopResumedActivityIfNeeded(reason);
+ if (mResumedActivity != null && topResumed != null && topResumed.isEmbedded()
+ && topResumed.getTaskFragment().getAdjacentTaskFragment() == this) {
+ // Explicitly updates the last resumed Activity if the resumed activity is
+ // adjacent to the top-resumed embedded activity.
+ mAtmService.setLastResumedActivityUncheckLocked(mResumedActivity, reason);
+ }
if (r == null && prevR.mDisplayContent != null
&& prevR.mDisplayContent.getFocusedRootTask() == null) {
// Only need to notify DWPC when no activity will resume.
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index dc500a2748cf..55eeaf22cca8 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -192,7 +192,18 @@ class WallpaperWindowToken extends WindowToken {
void setVisibility(boolean visible) {
if (mVisibleRequested != visible) {
// Before setting mVisibleRequested so we can track changes.
- mTransitionController.collect(this);
+ final WindowState wpTarget = mDisplayContent.mWallpaperController.getWallpaperTarget();
+ final boolean isTargetNotCollectedActivity = wpTarget != null
+ && wpTarget.mActivityRecord != null
+ && !mTransitionController.isCollecting(wpTarget.mActivityRecord);
+ // Skip collecting requesting-invisible wallpaper if the wallpaper target is an activity
+ // and it is not collected. Because the visibility change may be called after the
+ // transition of activity is finished, e.g. WallpaperController#hideWallpapers from
+ // hiding surface of the target. Then if there is a next transition, the wallpaper
+ // change may be collected into the unrelated transition and cause a weird animation.
+ if (!isTargetNotCollectedActivity || visible) {
+ mTransitionController.collect(this);
+ }
setVisibleRequested(visible);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index daf8129f1683..7e2ffd486c7e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -160,7 +160,7 @@ public abstract class WindowManagerInternal {
/**
* Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
* false.
*
* @param forceSend Send the windows for accessibility even if they haven't changed.
@@ -173,7 +173,7 @@ public abstract class WindowManagerInternal {
/**
* Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
* true.
* TODO(b/322444245): Remove screenSize parameter by getting it from
* DisplayManager#getDisplay(int).getRealSize() on the a11y side.
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 70224db061c7..88c47f3cc5f8 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -117,7 +117,6 @@ static struct {
jmethodID notifySensorAccuracy;
jmethodID notifyStickyModifierStateChanged;
jmethodID notifyStylusGestureStarted;
- jmethodID isInputMethodConnectionActive;
jmethodID notifyVibratorState;
jmethodID filterInputEvent;
jmethodID interceptKeyBeforeQueueing;
@@ -311,6 +310,7 @@ public:
void setStylusButtonMotionEventsEnabled(bool enabled);
FloatPoint getMouseCursorPosition(int32_t displayId);
void setStylusPointerIconEnabled(bool enabled);
+ void setInputMethodConnectionIsActive(bool isActive);
/* --- InputReaderPolicyInterface implementation --- */
@@ -453,6 +453,9 @@ private:
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
+
+ // True if there is an active input method connection.
+ bool isInputMethodConnectionActive{false};
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
@@ -1507,11 +1510,8 @@ void NativeInputManager::notifyStylusGestureStarted(int32_t deviceId, nsecs_t ev
}
bool NativeInputManager::isInputMethodConnectionActive() {
- JNIEnv* env = jniEnv();
- const jboolean result =
- env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isInputMethodConnectionActive);
- checkAndClearExceptionFromCallback(env, "isInputMethodConnectionActive");
- return result;
+ std::scoped_lock _l(mLock);
+ return mLocked.isInputMethodConnectionActive;
}
std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay(
@@ -1862,6 +1862,15 @@ void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
InputReaderConfiguration::Change::DISPLAY_INFO);
}
+void NativeInputManager::setInputMethodConnectionIsActive(bool isActive) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ mLocked.isInputMethodConnectionActive = isActive;
+ } // release lock
+
+ mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive);
+}
+
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2854,6 +2863,12 @@ static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeI
}
}
+static void nativeSetInputMethodConnectionIsActive(JNIEnv* env, jobject nativeImplObj,
+ jboolean isActive) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setInputMethodConnectionIsActive(isActive);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2964,6 +2979,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
(void*)nativeSetAccessibilitySlowKeysThreshold},
{"setAccessibilityStickyKeysEnabled", "(Z)V",
(void*)nativeSetAccessibilityStickyKeysEnabled},
+ {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
};
#define FIND_CLASS(var, className) \
@@ -3028,9 +3044,6 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.notifyStylusGestureStarted, clazz, "notifyStylusGestureStarted",
"(IJ)V");
- GET_METHOD_ID(gServiceClassInfo.isInputMethodConnectionActive, clazz,
- "isInputMethodConnectionActive", "()Z");
-
GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V");
GET_METHOD_ID(gServiceClassInfo.notifyNoFocusedWindowAnr, clazz, "notifyNoFocusedWindowAnr",
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 912ff4ae2022..755a7f884a95 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -162,7 +162,7 @@
<xs:element type="usiVersion" name="usiVersion">
<xs:annotation name="final"/>
</xs:element>
- <xs:element type="lowBrightnessMode" name="lowBrightness">
+ <xs:element type="evenDimmerMode" name="evenDimmer">
<xs:attribute name="enabled" type="xs:boolean" use="optional"/>
<xs:annotation name="final"/>
</xs:element>
@@ -221,7 +221,7 @@
</xs:restriction>
</xs:simpleType>
- <xs:complexType name="lowBrightnessMode">
+ <xs:complexType name="evenDimmerMode">
<xs:sequence>
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
maxOccurs="1">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 3c708900c64e..4fa77d963733 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -109,11 +109,11 @@ package com.android.server.display.config {
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+ method public final com.android.server.display.config.EvenDimmerMode getEvenDimmer();
method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout();
method public final com.android.server.display.config.SensorDetails getLightSensor();
- method public final com.android.server.display.config.LowBrightnessMode getLowBrightness();
method public com.android.server.display.config.LuxThrottling getLuxThrottling();
method @Nullable public final String getName();
method public com.android.server.display.config.PowerThrottlingConfig getPowerThrottlingConfig();
@@ -146,11 +146,11 @@ package com.android.server.display.config {
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+ method public final void setEvenDimmer(com.android.server.display.config.EvenDimmerMode);
method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
- method public final void setLowBrightness(com.android.server.display.config.LowBrightnessMode);
method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
method public final void setName(@Nullable String);
method public void setPowerThrottlingConfig(com.android.server.display.config.PowerThrottlingConfig);
@@ -182,6 +182,19 @@ package com.android.server.display.config {
method public java.util.List<java.lang.String> getQuirk();
}
+ public class EvenDimmerMode {
+ ctor public EvenDimmerMode();
+ method public java.util.List<java.lang.Float> getBacklight();
+ method public java.util.List<java.lang.Float> getBrightness();
+ method public boolean getEnabled();
+ method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap();
+ method public java.util.List<java.lang.Float> getNits();
+ method public java.math.BigDecimal getTransitionPoint();
+ method public void setEnabled(boolean);
+ method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap);
+ method public void setTransitionPoint(java.math.BigDecimal);
+ }
+
public class HbmTiming {
ctor public HbmTiming();
method @NonNull public final java.math.BigInteger getTimeMaxSecs_all();
@@ -250,19 +263,6 @@ package com.android.server.display.config {
method public java.util.List<java.math.BigInteger> getItem();
}
- public class LowBrightnessMode {
- ctor public LowBrightnessMode();
- method public java.util.List<java.lang.Float> getBacklight();
- method public java.util.List<java.lang.Float> getBrightness();
- method public boolean getEnabled();
- method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap();
- method public java.util.List<java.lang.Float> getNits();
- method public java.math.BigDecimal getTransitionPoint();
- method public void setEnabled(boolean);
- method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap);
- method public void setTransitionPoint(java.math.BigDecimal);
- }
-
public class LuxThrottling {
ctor public LuxThrottling();
method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 6aeb4fd53905..28fe5dfc9bc1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1671,19 +1671,29 @@ final class DevicePolicyEngine {
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Local Policies: ");
+ pw.increaseIndent();
for (int i = 0; i < mLocalPolicies.size(); i++) {
- for (PolicyKey policy : mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()) {
- PolicyState<?> policyState = mLocalPolicies.get(
- mLocalPolicies.keyAt(i)).get(policy);
- pw.println(policyState);
+ int userId = mLocalPolicies.keyAt(i);
+ pw.printf("User %d:\n", userId);
+ pw.increaseIndent();
+ for (PolicyKey policy : mLocalPolicies.get(userId).keySet()) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ policyState.dump(pw);
+ pw.println();
}
+ pw.decreaseIndent();
}
+ pw.decreaseIndent();
pw.println();
+
pw.println("Global Policies: ");
+ pw.increaseIndent();
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
- pw.println(policyState);
+ policyState.dump(pw);
+ pw.println();
}
+ pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 49ffb0d01019..d234dee3c8f7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -325,8 +325,22 @@ final class EnforcingAdmin {
@Override
public String toString() {
- return "EnforcingAdmin { mPackageName= " + mPackageName + ", mComponentName= "
- + mComponentName + ", mAuthorities= " + mAuthorities + ", mUserId= "
- + mUserId + ", mIsRoleAuthority= " + mIsRoleAuthority + " }\n";
+ StringBuilder sb = new StringBuilder();
+ sb.append("EnforcingAdmin { mPackageName= ");
+ sb.append(mPackageName);
+ if (mComponentName != null) {
+ sb.append(", mComponentName= ");
+ sb.append(mComponentName);
+ }
+ if (mAuthorities != null) {
+ sb.append(", mAuthorities= ");
+ sb.append(mAuthorities);
+ }
+ sb.append(", mUserId= ");
+ sb.append(mUserId);
+ sb.append(", mIsRoleAuthority= ");
+ sb.append(mIsRoleAuthority);
+ sb.append(" }");
+ return sb.toString();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index c2e370eeec44..c544ebc008b9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,6 +19,7 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
+import android.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -195,6 +196,33 @@ final class PolicyState<V> {
+ ",\nmCurrentResolvedPolicy= \n\t" + mCurrentResolvedPolicy + " }";
}
+ public void dump(IndentingPrintWriter pw) {
+ pw.println(mPolicyDefinition.getPolicyKey());
+ pw.increaseIndent();
+
+ pw.println("Per-admin Policy");
+ pw.increaseIndent();
+ if (mPoliciesSetByAdmins.size() == 0) {
+ pw.println("null");
+ } else {
+ for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
+ pw.println(admin);
+ pw.increaseIndent();
+ pw.println(mPoliciesSetByAdmins.get(admin));
+ pw.decreaseIndent();
+ }
+ }
+ pw.decreaseIndent();
+
+ pw.printf("Resolved Policy (%s):\n",
+ mPolicyDefinition.getResolutionMechanism().getClass().getSimpleName());
+ pw.increaseIndent();
+ pw.println(mCurrentResolvedPolicy);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+
void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
mPolicyDefinition.saveToXml(serializer);
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 9115f952b724..08155c7b3f98 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -3158,13 +3158,6 @@ public class VpnTest extends VpnTestBase {
assertEquals(profile, ikev2VpnProfile.toVpnProfile());
}
- private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
- assertNotNull(nc);
- VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
- assertNotNull(ti);
- assertEquals(type, ti.getType());
- }
-
// Make it public and un-final so as to spy it
public class TestDeps extends Vpn.Dependencies {
TestDeps() {}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index e71ea263983a..74260cdd497c 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_USB" />
+ <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 5a022c0f5d27..494a6677e633 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -913,7 +913,7 @@ public final class DisplayDeviceConfigTest {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
- assertTrue(mDisplayDeviceConfig.getLbmEnabled());
+ assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
assertEquals(0.0001f, mDisplayDeviceConfig.getBacklightFromBrightness(0.1f), ZERO_DELTA);
assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
}
@@ -1637,7 +1637,7 @@ public final class DisplayDeviceConfigTest {
}
private String evenDimmerConfig(boolean enabled) {
- return (enabled ? "<lowBrightness enabled=\"true\">" : "<lowBrightness enabled=\"false\">")
+ return (enabled ? "<evenDimmer enabled=\"true\">" : "<evenDimmer enabled=\"false\">")
+ " <transitionPoint>0.1</transitionPoint>\n"
+ " <nits>0.2</nits>\n"
+ " <nits>2.0</nits>\n"
@@ -1662,7 +1662,7 @@ public final class DisplayDeviceConfigTest {
+ " <value>100</value> <nits>1.0</nits>\n"
+ " </point>\n"
+ " </luxToMinimumNitsMap>\n"
- + "</lowBrightness>";
+ + "</evenDimmer>";
}
private void mockDeviceConfigs() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 869cec8d733d..b7fa7ead4c81 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -45,6 +45,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
@@ -56,6 +57,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -68,8 +70,11 @@ import android.companion.virtual.IVirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.flags.Flags;
import android.compat.testing.PlatformCompatChangeRule;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
@@ -107,6 +112,7 @@ import android.os.SystemProperties;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.test.mock.MockContentResolver;
import android.util.SparseArray;
import android.view.ContentRecordingSession;
@@ -153,6 +159,7 @@ import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -327,6 +334,7 @@ public class DisplayManagerServiceTest {
}
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+ private final FakeSettingsProvider mFakeSettingsProvider = new FakeSettingsProvider();
@Mock DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock IMediaProjectionManager mMockProjectionService;
@@ -384,6 +392,7 @@ public class DisplayManagerServiceTest {
ApplicationProvider.getApplicationContext().createDisplayContext(display)));
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
+ resolver.addProvider(Settings.AUTHORITY, mFakeSettingsProvider);
mResources = Mockito.spy(mContext.getResources());
mPowerHandler = new Handler(Looper.getMainLooper());
manageDisplaysPermission(/* granted= */ false);
@@ -2893,6 +2902,110 @@ public class DisplayManagerServiceTest {
FLOAT_TOLERANCE);
}
+ @Test
+ public void testResolutionChangeGetsBackedUp_FeatureFlagFalse() throws Exception {
+ when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(false);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+
+ Display.Mode[] modes = new Display.Mode[2];
+ modes[0] = new Display.Mode(/*id=*/ 101, /*width=*/ 100, /*height=*/ 200, /*rr=*/ 60);
+ modes[1] = new Display.Mode(/*id=*/ 101, /*width=*/ 200, /*height=*/ 400, /*rr=*/ 60);
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, modes);
+ waitForIdleHandler(displayManager.getDisplayHandler());
+
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ bs.setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(100, 200, 0));
+ try {
+ Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SCREEN_RESOLUTION_MODE);
+ fail("SettingNotFoundException should have been thrown.");
+ } catch (SettingNotFoundException expected) {
+ }
+ }
+
+ @Test
+ public void testResolutionChangeGetsBackedUp() throws Exception {
+ when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+
+ Display.Mode[] modes = new Display.Mode[2];
+ modes[0] = new Display.Mode(/*id=*/ 101, /*width=*/ 100, /*height=*/ 200, /*rr=*/ 60);
+ modes[1] = new Display.Mode(/*id=*/ 101, /*width=*/ 200, /*height=*/ 400, /*rr=*/ 60);
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, modes);
+ waitForIdleHandler(displayManager.getDisplayHandler());
+
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ bs.setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(100, 200, 0));
+ assertEquals(Settings.Secure.RESOLUTION_MODE_HIGH,
+ Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SCREEN_RESOLUTION_MODE));
+
+ bs.setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(200, 400, 0));
+ assertEquals(Settings.Secure.RESOLUTION_MODE_FULL,
+ Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SCREEN_RESOLUTION_MODE));
+ }
+
+ @Test
+ public void testResolutionGetsRestored() throws Exception {
+ when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+
+ displayManager.systemReady(false /* safeMode */);
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentMatcher<IntentFilter> matchesFilter =
+ (filter) -> Intent.ACTION_SETTING_RESTORED.equals(filter.getAction(0));
+ verify(mContext).registerReceiver(receiverCaptor.capture(), argThat(matchesFilter));
+ BroadcastReceiver receiver = receiverCaptor.getValue();
+
+ Display.Mode emptyMode = new Display.Mode.Builder().build();
+ Display.Mode[] modes = new Display.Mode[2];
+ modes[0] = new Display.Mode(/*id=*/ 101, /*width=*/ 100, /*height=*/ 200, /*rr=*/ 60);
+ modes[1] = new Display.Mode(/*id=*/ 102, /*width=*/ 200, /*height=*/ 400, /*rr=*/ 60);
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, modes);
+ waitForIdleHandler(displayManager.getDisplayHandler());
+
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ // Get the current display mode, ensure it is null
+ Display.Mode prevMode = bs.getUserPreferredDisplayMode(Display.DEFAULT_DISPLAY);
+ assertEquals(emptyMode, prevMode);
+
+ // Set a new mode (FULL) via restore
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.SCREEN_RESOLUTION_MODE, Settings.Secure.RESOLUTION_MODE_FULL);
+ Intent restoreIntent = new Intent(Intent.ACTION_SETTING_RESTORED);
+ restoreIntent.putExtra(Intent.EXTRA_SETTING_NAME, Settings.Secure.SCREEN_RESOLUTION_MODE);
+ receiver.onReceive(mContext, restoreIntent);
+
+ Display.Mode newMode = bs.getUserPreferredDisplayMode(Display.DEFAULT_DISPLAY);
+ assertEquals(modes[1], newMode);
+ }
+
+ @Test
+ public void testResolutionGetsRestored_FeatureFlagFalse() throws Exception {
+ when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(false);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+
+ displayManager.systemReady(false /* safeMode */);
+ ArgumentMatcher<IntentFilter> matchesFilter =
+ (filter) -> Intent.ACTION_SETTING_RESTORED.equals(filter.getAction(0));
+ verify(mContext, times(0)).registerReceiver(any(BroadcastReceiver.class),
+ argThat(matchesFilter));
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -3116,6 +3229,29 @@ public class DisplayManagerServiceTest {
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+ Display.Mode[] modes) {
+ FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+ DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+ displayDeviceInfo.supportedModes = modes;
+ displayDeviceInfo.modeId = 101;
+ displayDeviceInfo.type = Display.TYPE_INTERNAL;
+ displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
+ displayDeviceInfo.width = displayDeviceInfo.supportedModes[0].getPhysicalWidth();
+ displayDeviceInfo.height = displayDeviceInfo.supportedModes[0].getPhysicalHeight();
+ final Rect zeroRect = new Rect();
+ displayDeviceInfo.displayCutout = new DisplayCutout(
+ Insets.of(0, 10, 0, 0),
+ zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
+ displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+ displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
+ displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+ displayManager.getDisplayDeviceRepository()
+ .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+ return displayDevice;
+ }
+
+ private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+
float[] refreshRates) {
return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN);
}
@@ -3307,6 +3443,7 @@ public class DisplayManagerServiceTest {
private class FakeDisplayDevice extends DisplayDevice {
private DisplayDeviceInfo mDisplayDeviceInfo;
+ private Display.Mode mPreferredMode = new Display.Mode.Builder().build();
FakeDisplayDevice() {
super(mMockDisplayAdapter, /* displayToken= */ null, /* uniqueId= */ "", mContext);
@@ -3325,5 +3462,23 @@ public class DisplayManagerServiceTest {
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
return mDisplayDeviceInfo;
}
+
+ @Override
+ public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) {
+ for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) {
+ if (mode.matchesIfValid(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate())) {
+ mPreferredMode = mode;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public Display.Mode getUserPreferredDisplayModeLocked() {
+ return mPreferredMode;
+ }
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index 749c400f819e..21e066dd0922 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -32,7 +32,7 @@ import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
-private const val userId = UserHandle.USER_CURRENT
+private const val USER_ID = UserHandle.USER_CURRENT
class BrightnessLowLuxModifierTest {
@@ -85,7 +85,7 @@ class BrightnessLowLuxModifierTest {
.thenReturn(1.0f)
- whenever(mockDisplayDeviceConfig.lowBrightnessTransitionPoint).thenReturn(TRANSITION_POINT)
+ whenever(mockDisplayDeviceConfig.evenDimmerTransitionPoint).thenReturn(TRANSITION_POINT)
testHandler.flush()
}
@@ -94,7 +94,7 @@ class BrightnessLowLuxModifierTest {
fun testSettingOffDisablesModifier() {
// test transition point ensures brightness doesn't drop when setting is off.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID)
modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.recalculateLowerBound()
testHandler.flush()
@@ -112,9 +112,9 @@ class BrightnessLowLuxModifierTest {
fun testLuxRestrictsBrightnessRange() {
// test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, USER_ID)
modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.onAmbientLuxChange(400.0f)
testHandler.flush()
@@ -130,9 +130,9 @@ class BrightnessLowLuxModifierTest {
fun testUserRestrictsBrightnessRange() {
// test that user minimum nits setting prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, USER_ID)
modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.recalculateLowerBound()
testHandler.flush()
@@ -149,9 +149,9 @@ class BrightnessLowLuxModifierTest {
fun testOnToOff() {
// test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.onAmbientLuxChange(400.0f)
testHandler.flush()
@@ -162,7 +162,7 @@ class BrightnessLowLuxModifierTest {
assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) // off
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
modifier.recalculateLowerBound()
testHandler.flush()
@@ -177,9 +177,9 @@ class BrightnessLowLuxModifierTest {
fun testOffToOn() {
// test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) // off
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.onAmbientLuxChange(400.0f)
testHandler.flush()
@@ -191,7 +191,7 @@ class BrightnessLowLuxModifierTest {
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
modifier.recalculateLowerBound()
testHandler.flush()
@@ -206,9 +206,9 @@ class BrightnessLowLuxModifierTest {
fun testDisabledWhenAutobrightnessIsOff() {
// test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.onAmbientLuxChange(400.0f)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index ec27f9d220dc..f391e409a717 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -38,6 +38,9 @@ import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.Time;
import android.os.Handler;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.System;
@@ -51,6 +54,7 @@ import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.server.SystemService;
+import com.android.server.display.feature.flags.Flags;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -94,6 +98,9 @@ public class ColorDisplayServiceTest {
@Rule
public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
mContext = Mockito.spy(new ContextWrapper(
@@ -1003,6 +1010,20 @@ public class ColorDisplayServiceTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ public void ensureRbcDisabledWhenEvenDimmerEnabled() {
+ // If rbc & even dimmer are enabled
+ doReturn(true).when(mResourcesSpy).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+ doReturn(true).when(mResourcesSpy).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ startService();
+
+ // ensure rbc isn't enabled, since even dimmer is the successor.
+ assertThat(ColorDisplayManager.isReduceBrightColorsAvailable(mContext)).isFalse();
+ }
+
+ @Test
public void displayWhiteBalance_enabled() {
setDisplayWhiteBalanceEnabled(true);
setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 8717a0500e57..403930d96a12 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -86,11 +86,12 @@ import java.util.List;
// LINT.IfChange
/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y enabled.
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
+ * enabled.
* TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest
* after completing the flag migration.
*/
-@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
+@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
public class AccessibilityWindowManagerTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index f44879fa54d9..9083a1e28e2c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -89,11 +89,11 @@ import java.util.Arrays;
import java.util.List;
/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
* TODO(b/322444245): Merge with AccessibilityWindowManagerTest
* after completing the flag migration.
*/
-@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
+@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
public class AccessibilityWindowManagerWithAccessibilityWindowTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt
new file mode 100644
index 000000000000..e2b9944f2cc7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.server.appwidget
+
+import android.util.SizeF
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.server.appwidget.AppWidgetXmlUtil.deserializeWidgetSizesStr
+import com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppWidgetXmlUtilTest {
+
+ private val sizes = ArrayList<SizeF>()
+ private val sizeStr = "1.0x2.1,-9.91x6291.134,0.0x0.0"
+
+ @Before
+ fun setup() {
+ sizes.add(SizeF(1.0f, 2.1f))
+ sizes.add(SizeF(-9.91f, 6291.134f))
+ sizes.add(SizeF(0f, 0f))
+ }
+
+ @Test
+ fun serializeWidgetSizes() {
+ val serializedSizeStr = serializeWidgetSizes(sizes)
+
+ assertThat(serializedSizeStr).isEqualTo(sizeStr)
+ }
+
+ @Test
+ fun deserializeWidgetSizesStr() {
+ val deserializedSizes = deserializeWidgetSizesStr(sizeStr)
+
+ assertThat(deserializedSizes).isEqualTo(sizes)
+ }
+
+ @Test
+ fun deserializeInvalidWidgetSizesStr1() {
+ assertThat(deserializeWidgetSizesStr("abc,def")).isEqualTo(null)
+ }
+
+ @Test
+ fun deserializeInvalidWidgetSizesStr2() {
+ assertThat(deserializeWidgetSizesStr("+30x9,90")).isEqualTo(null)
+ }
+
+ @Test
+ fun deserializeNullWidgetSizesStr1() {
+ assertThat(deserializeWidgetSizesStr(null)).isEqualTo(null)
+ }
+
+ @Test
+ fun deserializeEmptyWidgetSizesStr1() {
+ assertThat(deserializeWidgetSizesStr("")).isEqualTo(null)
+ }
+
+ @Test
+ fun deserializeEmptyWidgetSizesStr2() {
+ assertThat(deserializeWidgetSizesStr(",")).isEqualTo(ArrayList<SizeF>())
+ }
+
+ @Test
+ fun deserializeEmptyWidgetSizesStr3() {
+ assertThat(deserializeWidgetSizesStr(",,,")).isEqualTo(ArrayList<SizeF>())
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index beafeec20bb5..e42acba4da7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3110,6 +3110,10 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testCloseToSquareFixedOrientation() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // No test needed as decor insets no longer affects orientation.
+ return;
+ }
// create a square display
final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
.setSystemDecorations(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
new file mode 100644
index 000000000000..4afc8ac6c599
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.am.PendingIntentRecord;
+import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for the {@link BackgroundActivityStartController} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:BackgroundActivityStartControllerExemptionTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class BackgroundActivityStartControllerExemptionTests {
+
+ private static final int REGULAR_UID_1 = 10100;
+ private static final int REGULAR_UID_2 = 10200;
+ private static final int NO_UID = -1;
+ private static final int REGULAR_PID_1 = 11100;
+ private static final int REGULAR_PID_1_1 = 11101;
+ private static final int REGULAR_PID_2 = 11200;
+ private static final int NO_PID = -1;
+ private static final String REGULAR_PACKAGE_1 = "package.app1";
+ private static final String REGULAR_PACKAGE_2 = "package.app2";
+
+ private static final Intent TEST_INTENT = new Intent()
+ .setComponent(new ComponentName("package.app3", "someClass"));
+
+ @Rule
+ public final ExtendedMockitoRule extendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build();
+
+ TestableBackgroundActivityStartController mController;
+ @Mock
+ ActivityMetricsLogger mActivityMetricsLogger;
+ @Mock
+ WindowProcessController mCallerApp;
+ DeviceConfigStateHelper mDeviceConfig = new DeviceConfigStateHelper(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER);
+ @Mock
+ ActivityRecord mResultRecord;
+
+ @Mock
+ ActivityTaskManagerService mService;
+ @Mock
+ Context /* mService. */ mContext;
+ @Mock
+ PackageManagerInternal /* mService. */ mPackageManagerInternal;
+ @Mock
+ RootWindowContainer /* mService. */ mRootWindowContainer;
+ @Mock
+ AppOpsManager mAppOpsManager;
+ MirrorActiveUids mActiveUids = new MirrorActiveUids();
+ WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
+
+ @Mock
+ ActivityTaskSupervisor mSupervisor;
+ @Mock
+ RecentTasks /* mSupervisor. */ mRecentTasks;
+
+ @Mock
+ PendingIntentRecord mPendingIntentRecord; // just so we can pass a non-null instance
+
+ record BalAllowedLog(String packageName, int code) {
+ }
+
+ List<String> mShownToasts = new ArrayList<>();
+ List<BalAllowedLog> mBalAllowedLogs = new ArrayList<>();
+
+ ActivityOptions mCheckedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+
+ class TestableBackgroundActivityStartController extends BackgroundActivityStartController {
+ private Set<Pair<Integer, Integer>> mBalPermissionUidPidPairs = new HashSet<>();
+
+ TestableBackgroundActivityStartController(ActivityTaskManagerService service,
+ ActivityTaskSupervisor supervisor) {
+ super(service, supervisor);
+ }
+
+ @Override
+ protected void writeBalAllowedLog(String activityName, int code,
+ BalState state) {
+ mBalAllowedLogs.add(new BalAllowedLog(activityName, code));
+ }
+
+ @Override
+ boolean hasBalPermission(int uid, int pid) {
+ return mBalPermissionUidPidPairs.contains(Pair.create(uid, pid));
+ }
+
+ void addBalPermission(int uid, int pid) {
+ mBalPermissionUidPidPairs.add(Pair.create(uid, pid));
+ }
+
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // wire objects
+ mService.mTaskSupervisor = mSupervisor;
+ mService.mContext = mContext;
+ setViaReflection(mService, "mActiveUids", mActiveUids);
+ when(mService.getPackageManagerInternalLocked()).thenReturn(
+ mPackageManagerInternal);
+ mService.mRootWindowContainer = mRootWindowContainer;
+ when(mService.getAppOpsManager()).thenReturn(mAppOpsManager);
+ setViaReflection(mService, "mProcessMap", mProcessMap);
+
+ //Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController);
+ setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
+
+ mController = new TestableBackgroundActivityStartController(mService, mSupervisor);
+
+ // nicer toString
+ when(mPendingIntentRecord.toString()).thenReturn("PendingIntentRecord");
+
+ // safe defaults
+ when(mAppOpsManager.checkOpNoThrow(
+ eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION),
+ anyInt(), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT);
+ when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+ BalVerdict.BLOCK);
+ }
+
+ private void setViaReflection(Object o, String property, Object value) {
+ try {
+ Field field = o.getClass().getDeclaredField(property);
+ field.setAccessible(true);
+ field.set(o, value);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ throw new IllegalArgumentException("Cannot set " + property + " of " + o.getClass(), e);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testNoExemption() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ assertWithMessage(balState.toString()).that(balState.isPendingIntent()).isTrue();
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+ balState);
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+ balState);
+
+ balState.setResultForCaller(callerVerdict);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BackgroundActivityStartController.BAL_BLOCK);
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BackgroundActivityStartController.BAL_BLOCK);
+ }
+
+ @Test
+ public void testCaller_appHasVisibleWindow() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ when(mService.hasActiveVisibleWindow(eq(callingUid))).thenReturn(true);
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+ balState);
+ balState.setResultForCaller(callerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_VISIBLE_WINDOW);
+ }
+
+ @Test
+ public void testRealCaller_appHasVisibleWindow() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ when(mService.hasActiveVisibleWindow(eq(realCallingUid))).thenReturn(true);
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+ balState);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_VISIBLE_WINDOW);
+ }
+
+ @Test
+ public void testCaller_appAllowedByBLPC() {
+ // This covers the cases
+ // - The app has an activity in the back stack of the foreground task.
+ // - The app has an activity in the back stack of an existing task on the Recents screen.
+ // - The app has an activity that started very recently.
+ // - The app called finish() on an activity very recently.
+ // - The app has a service that is bound by a different, visible app.
+
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+ new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+ balState);
+ balState.setResultForCaller(callerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_FOREGROUND);
+ }
+
+ @Test
+ public void testRealCaller_appAllowedByBLPC() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ when(
+ mService.getProcessController(eq(realCallingPid), eq(realCallingUid))).thenReturn(
+ mCallerApp);
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+ when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+ new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+ balState);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_FOREGROUND);
+ }
+
+ // TODO? The app has one of the following services that is bound by the system. These
+ // services might need to launch a UI.
+
+ @Test
+ public void testRealCaller_appAllowedByBLPCforOtherProcess() {
+ // The app has a service that is bound by a different, visible app. The app bound to the
+ // service must remain visible for the app in the background to start activities
+ // successfully.
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
+ WindowProcessController otherProcess = Mockito.mock(WindowProcessController.class);
+ mProcessMap.put(callingPid, mCallerApp);
+ mProcessMap.put(REGULAR_PID_1_1, otherProcess);
+ setViaReflection(mService, "mProcessMap", mProcessMap);
+ when(
+ mService.getProcessController(eq(realCallingPid), eq(realCallingUid))).thenReturn(
+ mCallerApp);
+ when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+ when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+ BalVerdict.BLOCK);
+ when(otherProcess.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+ new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+ balState);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_FOREGROUND);
+ }
+
+ @Test
+ public void testRealCaller_isCompanionApp() {
+ // The app has a service that is bound by a different, visible app. The app bound to the
+ // service must remain visible for the app in the background to start activities
+ // successfully.
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ final int realCallingUserId = UserHandle.getUserId(realCallingUid);
+ when(mService.isAssociatedCompanionApp(eq(realCallingUserId),
+ eq(realCallingUid))).thenReturn(true);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+ balState);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_ALLOWLISTED_COMPONENT);
+ }
+
+ @Test
+ public void testCaller_balPermission() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ mController.addBalPermission(callingUid, callingPid);
+ mController.addBalPermission(callingUid, NO_PID);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+ balState);
+ balState.setResultForCaller(callerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_PERMISSION);
+ }
+
+ @Test
+ public void testRealCaller_balPermission() {
+ // BAL allowed by permission. Requires explicit opt-in in options (hidden/not documented!).
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ mController.addBalPermission(realCallingUid, realCallingPid);
+ mController.addBalPermission(realCallingUid, NO_PID);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ checkedOptions.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ assertThat(balState.isPendingIntentBalAllowedByPermission()).isTrue();
+
+ // call
+ BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+ balState);
+ balState.setResultForRealCaller(realCallerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_PERMISSION);
+ }
+
+ @Test
+ public void testCaller_sawPermission() {
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = REGULAR_UID_2;
+ int realCallingPid = REGULAR_PID_2;
+
+ // setup state
+ when(mService.hasSystemAlertWindowPermission(eq(callingUid), eq(callingPid),
+ eq(callingPackage))).thenReturn(true);
+
+ // prepare call
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = mCheckedOptions;
+ BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+ callingPid, callingPackage, realCallingUid, realCallingPid, null,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // call
+ BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+ balState);
+ balState.setResultForCaller(callerVerdict);
+
+ // assertions
+ assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+ BAL_ALLOW_SAW_PERMISSION);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index ef131ac337b1..5aa4ba3e1d42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -62,7 +62,7 @@ import java.util.Map;
import java.util.Optional;
/**
- * Tests for the {@link ActivityStarter} class.
+ * Tests for the {@link BackgroundActivityStartController} class.
*
* Build/Install/Run:
* atest WmTests:BackgroundActivityStartControllerTests
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 5965fae74dcc..c77a4d650bb5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -70,6 +70,8 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -411,6 +413,10 @@ public class DisplayPolicyTests extends WindowTestsBase {
@Test
public void testUpdateDisplayConfigurationByDecor() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // No configuration update when flag enables.
+ return;
+ }
doReturn(NO_CUTOUT).when(mDisplayContent).calculateDisplayCutoutForRotation(anyInt());
final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent);
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index b96f39d7a4e1..85172e08423e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -123,6 +123,7 @@ import com.android.internal.policy.SystemBarUtils;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.DeviceStateController.DeviceState;
+import com.android.window.flags.Flags;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -703,6 +704,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
final int notchHeight = 100;
setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build());
@@ -944,6 +950,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testMoveToDifferentOrientationDisplay() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
setUpDisplaySizeWithApp(1000, 2500);
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
@@ -991,6 +1002,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testFixedOrientationRotateCutoutDisplay() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// Create a display with a notch/cutout
final int notchHeight = 60;
final int width = 1000;
@@ -1587,6 +1603,11 @@ public class SizeCompatTests extends WindowTestsBase {
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioLowerThanManifest() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, 1800)
.setNotch(200).setSystemDecorations(true).build();
mTask = new TaskBuilder(mSupervisor).setDisplay(display).build();
@@ -1934,6 +1955,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testLaunchWithFixedRotationTransform() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
final int dw = 1000;
final int dh = 2500;
final int notchHeight = 200;
@@ -3757,6 +3783,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// Align to center so that we don't overlap with the status bar
mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
@@ -4076,6 +4107,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// Set up portrait close to square display
setUpDisplaySizeWithApp(2200, 2280);
final DisplayContent display = mActivity.mDisplayContent;
@@ -4102,6 +4138,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// The display's app bounds will be (0, 100, 1000, 2350)
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500)
.setCanRotate(false)
@@ -4116,6 +4157,11 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// The display's app bounds will be (0, 100, 1000, 2150)
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300)
.setCanRotate(false)
@@ -4131,6 +4177,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// The display's app bounds will be (100, 0, 2350, 1000)
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000)
.setCanRotate(false)
@@ -4145,6 +4196,11 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
// The display's app bounds will be (100, 0, 2150, 1000)
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000)
.setCanRotate(false)
@@ -4393,6 +4449,11 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testUpdateResolvedBoundsPosition_alignToTop() {
+ if (Flags.insetsDecoupledConfiguration()) {
+ // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
+ // bounds no longer contains display cutout.
+ return;
+ }
final int notchHeight = 100;
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
.setNotch(notchHeight)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6b1bf26bfdff..3c5b12c68e1c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -898,6 +898,32 @@ public class TaskFragmentTest extends WindowTestsBase {
}
}
+ @Test
+ public void testSetResumedActivity() {
+ // Setup two activities in ActivityEmbedding split.
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment taskFragmentLeft = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .build();
+ final TaskFragment taskFragmentRight = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .build();
+ taskFragmentRight.setAdjacentTaskFragment(taskFragmentLeft);
+ taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+ final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity();
+ final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity();
+
+ // Ensure the focused app is updated when the right activity resumed.
+ taskFragmentRight.setResumedActivity(appRightTop, "test");
+ assertEquals(appRightTop, task.getDisplayContent().mFocusedApp);
+
+ // Ensure the focused app is updated when the left activity resumed.
+ taskFragmentLeft.setResumedActivity(appLeftTop, "test");
+ assertEquals(appLeftTop, task.getDisplayContent().mFocusedApp);
+ }
+
private WindowState createAppWindow(ActivityRecord app, String name) {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());