summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java11
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java223
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java77
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java160
-rw-r--r--core/api/current.txt6
-rw-r--r--core/api/module-lib-current.txt1
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/api/test-current.txt18
-rw-r--r--core/java/android/animation/ValueAnimator.java4
-rw-r--r--core/java/android/app/ActivityManager.java51
-rw-r--r--core/java/android/app/ActivityOptions.java3
-rw-r--r--core/java/android/app/AppOpsManager.java5
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/app/IUserSwitchObserver.aidl1
-rw-r--r--core/java/android/app/UserSwitchObserver.java3
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java2
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--core/java/android/content/ClipboardManager.java2
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/Intent.java7
-rw-r--r--core/java/android/content/pm/ServiceInfo.java2
-rw-r--r--core/java/android/hardware/Camera.java19
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java54
-rw-r--r--core/java/android/net/NetworkPolicyManager.java17
-rw-r--r--core/java/android/provider/Settings.java33
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java3
-rw-r--r--core/java/android/service/credentials/PermissionUtils.java13
-rw-r--r--core/java/android/util/FeatureFlagUtils.java4
-rw-r--r--core/java/android/view/ViewGroup.java21
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java40
-rw-r--r--core/java/android/view/autofill/AutofillManager.java26
-rw-r--r--core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java6
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java27
-rw-r--r--core/java/android/widget/TextView.java38
-rw-r--r--core/java/com/android/internal/os/TimeoutRecord.java35
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl3
-rw-r--r--core/jni/android_hardware_Camera.cpp7
-rw-r--r--core/jni/android_view_MotionEvent.cpp7
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/res/AndroidManifest.xml17
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java26
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewBase.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java)6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java)3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java)4
-rw-r--r--libs/dream/lowlight/Android.bp6
-rw-r--r--libs/dream/lowlight/res/values/config.xml3
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java122
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt138
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java111
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt118
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java61
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt82
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt9
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt8
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt29
-rw-r--r--libs/dream/lowlight/tests/Android.bp2
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java109
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt169
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java113
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt184
-rw-r--r--libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt125
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java20
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java7
-rw-r--r--media/java/android/media/projection/IMediaProjection.aidl10
-rw-r--r--media/java/android/media/projection/IMediaProjectionManager.aidl11
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java3
-rw-r--r--native/android/input.cpp3
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt26
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java32
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java50
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java4
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/Shell/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt394
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt11
-rw-r--r--packages/SystemUI/res/values/integers.xml8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt191
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt319
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt341
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java39
-rw-r--r--services/core/java/com/android/server/am/BroadcastFilter.java11
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java12
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java14
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java10
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java6
-rw-r--r--services/core/java/com/android/server/am/UserController.java11
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java24
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java7
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayAdapter.java7
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java32
-rw-r--r--services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java28
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java8
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java29
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java45
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java3
-rw-r--r--services/core/java/com/android/server/notification/BubbleExtractor.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java12
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java1
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java12
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java24
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java3
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java7
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java7
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java21
-rw-r--r--services/core/java/com/android/server/wm/Task.java4
-rw-r--r--services/core/java/com/android/server/wm/Transition.java13
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java9
-rw-r--r--services/core/java/com/android/server/wm/VrController.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java56
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java7
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java63
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java34
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java133
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java107
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java4
-rw-r--r--services/tests/uiservicestests/AndroidManifest.xml1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java11
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java67
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java107
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java9
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java36
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java5
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java26
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java7
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java20
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java52
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java46
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java1
-rw-r--r--telephony/java/com/android/internal/telephony/ISub.aidl4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt2
-rw-r--r--tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java30
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java81
-rw-r--r--wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java105
221 files changed, 4825 insertions, 1768 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 805dfafe5923..37ceb091f035 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1382,6 +1382,12 @@ public class JobInfo implements Parcelable {
* Calling this method will override any requirements previously defined
* by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
* want to call one of these methods.
+ *
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * an app must hold the {@link android.Manifest.permission#INTERNET} and
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
+ * schedule a job that requires a network.
+ *
* <p class="note">
* When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1438,6 +1444,11 @@ public class JobInfo implements Parcelable {
* otherwise you'll use the default network which may not meet this
* constraint.
*
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * an app must hold the {@link android.Manifest.permission#INTERNET} and
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
+ * schedule a job that requires a network.
+ *
* @param networkRequest The detailed description of the kind of network
* this job requires, or {@code null} if no specific kind of
* network is required. Defining a {@link NetworkSpecifier}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 32502eddc9f8..bf4f9a83b99c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -481,6 +481,10 @@ public class JobParameters implements Parcelable {
* such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
* a metered network when there is a surplus of metered data available.
*
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * this will return {@code null} if the app does not hold the permissions specified in
+ * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
+ *
* @return the network that should be used to perform any network requests
* for this job, or {@code null} if this job didn't set any required
* network type or if the job executed when there was no available network to use.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d94993d64995..3fe83a64b5ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -190,6 +191,14 @@ public class JobSchedulerService extends com.android.server.SystemService
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
+ /**
+ * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling
+ * a job with a connectivity constraint.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static Clock sSystemClock = Clock.systemUTC();
@@ -299,6 +308,14 @@ public class JobSchedulerService extends com.android.server.SystemService
private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
new RemoteCallbackList<>();
+ /**
+ * Cache of grant status of permissions, keyed by UID->PID->permission name. A missing value
+ * means the state has not been queried.
+ */
+ @GuardedBy("mPermissionCache")
+ private final SparseArray<SparseArrayMap<String, Boolean>> mPermissionCache =
+ new SparseArray<>();
+
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -365,6 +382,16 @@ public class JobSchedulerService extends com.android.server.SystemService
* A mapping of which uids are currently in the foreground to their effective bias.
*/
final SparseIntArray mUidBiasOverride = new SparseIntArray();
+ /**
+ * A cached mapping of uids to their current capabilities.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mUidCapabilities = new SparseIntArray();
+ /**
+ * A cached mapping of uids to their proc states.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mUidProcStates = new SparseIntArray();
/**
* Which uids are currently performing backups, so we shouldn't allow their jobs to run.
@@ -1042,6 +1069,10 @@ public class JobSchedulerService extends com.android.server.SystemService
final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ synchronized (mPermissionCache) {
+ // Something changed. Better clear the cached permission set.
+ mPermissionCache.remove(pkgUid);
+ }
// Purge the app's jobs if the whole package was just disabled. When this is
// the case the component name will be a bare package name.
if (pkgName != null && pkgUid != -1) {
@@ -1106,17 +1137,19 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
}
} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ synchronized (mPermissionCache) {
+ // Something changed. Better clear the cached permission set.
+ mPermissionCache.remove(pkgUid);
+ }
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
synchronized (mLock) {
- mUidToPackageCache.remove(uid);
- }
- } else {
- synchronized (mJobSchedulerStub.mPersistCache) {
- mJobSchedulerStub.mPersistCache.remove(pkgUid);
+ mUidToPackageCache.remove(pkgUid);
}
}
} else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
+ synchronized (mPermissionCache) {
+ mPermissionCache.remove(pkgUid);
+ }
if (DEBUG) {
Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")");
}
@@ -1135,6 +1168,14 @@ public class JobSchedulerService extends com.android.server.SystemService
mDebuggableApps.remove(pkgName);
mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid);
}
+ } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ synchronized (mLock) {
+ mUidBiasOverride.delete(pkgUid);
+ mUidCapabilities.delete(pkgUid);
+ mUidProcStates.delete(pkgUid);
+ }
+ }
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
synchronized (mLock) {
@@ -1155,6 +1196,14 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
mConcurrencyManager.onUserRemoved(userId);
+ synchronized (mPermissionCache) {
+ for (int u = mPermissionCache.size() - 1; u >= 0; --u) {
+ final int uid = mPermissionCache.keyAt(u);
+ if (userId == UserHandle.getUserId(uid)) {
+ mPermissionCache.removeAt(u);
+ }
+ }
+ }
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
// Has this package scheduled any jobs, such that we will take action
// if it were to be force-stopped?
@@ -1205,7 +1254,11 @@ public class JobSchedulerService extends com.android.server.SystemService
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
- mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = uid;
+ args.argi2 = procState;
+ args.argi3 = capability;
+ mHandler.obtainMessage(MSG_UID_STATE_CHANGED, args).sendToTarget();
}
@Override public void onUidGone(int uid, boolean disabled) {
@@ -1947,8 +2000,14 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- void updateUidState(int uid, int procState) {
+ void updateUidState(int uid, int procState, int capabilities) {
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid + " proc state changed to "
+ + ActivityManager.procStateToString(procState)
+ + " with capabilities=" + ActivityManager.getCapabilitiesSummary(capabilities));
+ }
synchronized (mLock) {
+ mUidProcStates.put(uid, procState);
final int prevBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
if (procState == ActivityManager.PROCESS_STATE_TOP) {
// Only use this if we are exactly the top app. All others can live
@@ -1962,6 +2021,12 @@ public class JobSchedulerService extends com.android.server.SystemService
} else {
mUidBiasOverride.delete(uid);
}
+ if (capabilities == ActivityManager.PROCESS_CAPABILITY_NONE
+ || procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ mUidCapabilities.delete(uid);
+ } else {
+ mUidCapabilities.put(uid, capabilities);
+ }
final int newBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
if (prevBias != newBias) {
if (DEBUG) {
@@ -1982,6 +2047,23 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ /**
+ * Return the current {@link ActivityManager#PROCESS_CAPABILITY_ALL capabilities}
+ * of the given UID.
+ */
+ public int getUidCapabilities(int uid) {
+ synchronized (mLock) {
+ return mUidCapabilities.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE);
+ }
+ }
+
+ /** Return the current proc state of the given UID. */
+ public int getUidProcState(int uid) {
+ synchronized (mLock) {
+ return mUidProcStates.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+ }
+ }
+
@Override
public void onDeviceIdleStateChanged(boolean deviceIdle) {
synchronized (mLock) {
@@ -2245,6 +2327,9 @@ public class JobSchedulerService extends com.android.server.SystemService
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ final IntentFilter uidFilter = new IntentFilter(Intent.ACTION_UID_REMOVED);
+ getContext().registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, uidFilter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
userFilter.addAction(Intent.ACTION_USER_ADDED);
getContext().registerReceiverAsUser(
@@ -2776,15 +2861,19 @@ public class JobSchedulerService extends com.android.server.SystemService
break;
case MSG_UID_STATE_CHANGED: {
- final int uid = message.arg1;
- final int procState = message.arg2;
- updateUidState(uid, procState);
+ final SomeArgs args = (SomeArgs) message.obj;
+ final int uid = args.argi1;
+ final int procState = args.argi2;
+ final int capabilities = args.argi3;
+ updateUidState(uid, procState, capabilities);
+ args.recycle();
break;
}
case MSG_UID_GONE: {
final int uid = message.arg1;
final boolean disabled = message.arg2 != 0;
- updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+ ActivityManager.PROCESS_CAPABILITY_NONE);
if (disabled) {
cancelJobsForUid(uid,
/* includeSourceApp */ true,
@@ -3748,18 +3837,38 @@ public class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * Returns whether the app has the permission granted.
+ * This currently only works for normal permissions and <b>DOES NOT</b> work for runtime
+ * permissions.
+ * TODO: handle runtime permissions
+ */
+ private boolean hasPermission(int uid, int pid, @NonNull String permission) {
+ synchronized (mPermissionCache) {
+ SparseArrayMap<String, Boolean> pidPermissions = mPermissionCache.get(uid);
+ if (pidPermissions == null) {
+ pidPermissions = new SparseArrayMap<>();
+ mPermissionCache.put(uid, pidPermissions);
+ }
+ final Boolean cached = pidPermissions.get(pid, permission);
+ if (cached != null) {
+ return cached;
+ }
+
+ final int result = getContext().checkPermission(permission, pid, uid);
+ final boolean permissionGranted = (result == PackageManager.PERMISSION_GRANTED);
+ pidPermissions.add(pid, permission, permissionGranted);
+ return permissionGranted;
+ }
+ }
+
+ /**
* Binder stub trampoline implementation
*/
final class JobSchedulerStub extends IJobScheduler.Stub {
- /**
- * Cache determination of whether a given app can persist jobs
- * key is uid of the calling app; value is undetermined/true/false
- */
- private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
-
// Enforce that only the app itself (or shared uid participant) can schedule a
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
+ // TODO(141645789): merge enforceValidJobRequest() with validateJob()
private void enforceValidJobRequest(int uid, int pid, JobInfo job) {
final PackageManager pm = getContext()
.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
@@ -3784,31 +3893,33 @@ public class JobSchedulerService extends com.android.server.SystemService
throw new IllegalArgumentException(
"Tried to schedule job for non-existent component: " + service);
}
+ // If we get this far we're good to go; all we need to do now is check
+ // whether the app is allowed to persist its scheduled work.
if (job.isPersisted() && !canPersistJobs(pid, uid)) {
throw new IllegalArgumentException("Requested job cannot be persisted without"
+ " holding android.permission.RECEIVE_BOOT_COMPLETED permission");
}
+ if (job.getRequiredNetwork() != null
+ && CompatChanges.isChangeEnabled(
+ REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
+ // All networking, including with the local network and even local to the device,
+ // requires the INTERNET permission.
+ if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) {
+ throw new SecurityException(Manifest.permission.INTERNET
+ + " required for jobs with a connectivity constraint");
+ }
+ if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) {
+ throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE
+ + " required for jobs with a connectivity constraint");
+ }
+ }
}
private boolean canPersistJobs(int pid, int uid) {
- // If we get this far we're good to go; all we need to do now is check
- // whether the app is allowed to persist its scheduled work.
- final boolean canPersist;
- synchronized (mPersistCache) {
- Boolean cached = mPersistCache.get(uid);
- if (cached != null) {
- canPersist = cached.booleanValue();
- } else {
- // Persisting jobs is tantamount to running at boot, so we permit
- // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
- // permission
- int result = getContext().checkPermission(
- android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
- canPersist = (result == PackageManager.PERMISSION_GRANTED);
- mPersistCache.put(uid, canPersist);
- }
- }
- return canPersist;
+ // Persisting jobs is tantamount to running at boot, so we permit
+ // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
+ // permission
+ return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED);
}
private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
@@ -4027,6 +4138,8 @@ public class JobSchedulerService extends com.android.server.SystemService
+ " not permitted to schedule jobs for other apps");
}
+ enforceValidJobRequest(callerUid, callerPid, job);
+
int result = validateJob(job, callerUid, callerPid, userId, packageName, null);
if (result != JobScheduler.RESULT_SUCCESS) {
return result;
@@ -4836,6 +4949,25 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.decreaseIndent();
}
+ boolean procStatePrinted = false;
+ for (int i = 0; i < mUidProcStates.size(); i++) {
+ int uid = mUidProcStates.keyAt(i);
+ if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
+ if (!procStatePrinted) {
+ procStatePrinted = true;
+ pw.println();
+ pw.println("Uid proc states:");
+ pw.increaseIndent();
+ }
+ pw.print(UserHandle.formatUid(uid));
+ pw.print(": ");
+ pw.println(ActivityManager.procStateToString(mUidProcStates.valueAt(i)));
+ }
+ }
+ if (procStatePrinted) {
+ pw.decreaseIndent();
+ }
+
boolean overridePrinted = false;
for (int i = 0; i < mUidBiasOverride.size(); i++) {
int uid = mUidBiasOverride.keyAt(i);
@@ -4854,6 +4986,25 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.decreaseIndent();
}
+ boolean capabilitiesPrinted = false;
+ for (int i = 0; i < mUidCapabilities.size(); i++) {
+ int uid = mUidCapabilities.keyAt(i);
+ if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
+ if (!capabilitiesPrinted) {
+ capabilitiesPrinted = true;
+ pw.println();
+ pw.println("Uid capabilities:");
+ pw.increaseIndent();
+ }
+ pw.print(UserHandle.formatUid(uid));
+ pw.print(": ");
+ pw.println(ActivityManager.getCapabilitiesSummary(mUidCapabilities.valueAt(i)));
+ }
+ }
+ if (capabilitiesPrinted) {
+ pw.decreaseIndent();
+ }
+
boolean uidMapPrinted = false;
for (int i = 0; i < mUidToPackageCache.size(); ++i) {
final int uid = mUidToPackageCache.keyAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index b080bf31fed4..8355e9c6da99 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -21,6 +21,7 @@ import static android.app.job.JobInfo.getPriorityString;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.Manifest;
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,6 +40,7 @@ import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.net.Network;
import android.net.Uri;
@@ -339,12 +341,13 @@ public final class JobServiceContext implements ServiceConnection {
job.changedAuthorities.toArray(triggeredAuthorities);
}
final JobInfo ji = job.getJob();
+ final Network passedNetwork = canGetNetworkInformation(job) ? job.network : null;
mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(),
ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities,
- job.network);
+ passedNetwork);
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
mMaxExecutionTimeMillis =
@@ -390,23 +393,27 @@ public final class JobServiceContext implements ServiceConnection {
.setFlags(Intent.FLAG_FROM_BACKGROUND);
boolean binding = false;
try {
- final int bindFlags;
+ final Context.BindServiceFlags bindFlags;
if (job.shouldTreatAsUserInitiatedJob()) {
- // TODO (191785864, 261999509): add an appropriate flag so user-initiated jobs
- // can bypass data saver
- bindFlags = Context.BIND_AUTO_CREATE
- | Context.BIND_ALMOST_PERCEPTIBLE
- | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
- | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ bindFlags = Context.BindServiceFlags.of(
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_ALMOST_PERCEPTIBLE
+ | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
+ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS
+ | Context.BIND_NOT_APP_COMPONENT_USAGE);
} else if (job.shouldTreatAsExpeditedJob()) {
- bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_ALMOST_PERCEPTIBLE
- | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
- | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ bindFlags = Context.BindServiceFlags.of(
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALMOST_PERCEPTIBLE
+ | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
+ | Context.BIND_NOT_APP_COMPONENT_USAGE);
} else {
- bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_NOT_PERCEPTIBLE
- | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ bindFlags = Context.BindServiceFlags.of(
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_NOT_PERCEPTIBLE
+ | Context.BIND_NOT_APP_COMPONENT_USAGE);
}
binding = mContext.bindServiceAsUser(intent, this, bindFlags,
UserHandle.of(job.getUserId()));
@@ -504,6 +511,37 @@ public final class JobServiceContext implements ServiceConnection {
}
}
+ private boolean canGetNetworkInformation(@NonNull JobStatus job) {
+ if (job.getJob().getRequiredNetwork() == null) {
+ // The job never had a network constraint, so we're not going to give it a network
+ // object. Add this check as an early return to avoid wasting cycles doing permission
+ // checks for this job.
+ return false;
+ }
+ // The calling app is doing the work, so use its UID, not the source UID.
+ final int uid = job.getUid();
+ if (CompatChanges.isChangeEnabled(
+ JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
+ final String pkgName = job.getServiceComponent().getPackageName();
+ if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) {
+ return false;
+ }
+ if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean hasPermissionForDelivery(int uid, @NonNull String pkgName,
+ @NonNull String permission) {
+ final int result = PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
+ PermissionChecker.PID_UNKNOWN, uid, pkgName, /* attributionTag */ null,
+ "network info via JS");
+ return result == PermissionChecker.PERMISSION_GRANTED;
+ }
+
@EconomicPolicy.AppAction
private static int getStartActionId(@NonNull JobStatus job) {
switch (job.getEffectivePriority()) {
@@ -603,6 +641,15 @@ public final class JobServiceContext implements ServiceConnection {
}
void informOfNetworkChangeLocked(Network newNetwork) {
+ if (newNetwork != null && mRunningJob != null && !canGetNetworkInformation(mRunningJob)) {
+ // The app can't get network information, so there's no point informing it of network
+ // changes. This case may happen if an app had scheduled network job and then
+ // started targeting U+ without requesting the required network permissions.
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping network change call because of missing permissions");
+ }
+ return;
+ }
if (mVerb != VERB_EXECUTING) {
Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob);
if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 4c55dac626d5..5246f2bf838b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -188,7 +188,7 @@ public final class BatteryController extends RestrictingController {
mLastReportedStatsdStablePower = stablePower;
}
if (mLastReportedStatsdBatteryNotLow == null
- || mLastReportedStatsdBatteryNotLow != stablePower) {
+ || mLastReportedStatsdBatteryNotLow != batteryNotLow) {
logDeviceWideConstraintStateToStatsd(JobStatus.CONSTRAINT_BATTERY_NOT_LOW,
batteryNotLow);
mLastReportedStatsdBatteryNotLow = batteryNotLow;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 3859d89c22cd..f6bdb9303a04 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -18,15 +18,18 @@ package com.android.server.job.controllers;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.job.JobInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.INetworkPolicyListener;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
@@ -47,6 +50,7 @@ import android.util.Log;
import android.util.Pools;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -98,13 +102,12 @@ public final class ConnectivityController extends RestrictingController implemen
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
| ConnectivityManager.BLOCKED_REASON_DOZE);
- // TODO(261999509): allow bypassing data saver & user-restricted. However, when we allow a UI
- // job to run while data saver restricts the app, we must ensure that we don't run regular
- // jobs when we put a hole in the data saver wall for the UI job
private static final int UNBYPASSABLE_UI_BLOCKED_REASONS =
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
- | ConnectivityManager.BLOCKED_REASON_DOZE);
+ | ConnectivityManager.BLOCKED_REASON_DOZE
+ | ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+ | ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED);
private static final int UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS =
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
@@ -113,6 +116,7 @@ public final class ConnectivityController extends RestrictingController implemen
| ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED);
private final ConnectivityManager mConnManager;
+ private final NetworkPolicyManager mNetPolicyManager;
private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
private final FlexibilityController mFlexibilityController;
@@ -241,6 +245,8 @@ public final class ConnectivityController extends RestrictingController implemen
*/
private final List<UidStats> mSortedStats = new ArrayList<>();
@GuardedBy("mLock")
+ private final SparseBooleanArray mBackgroundMeteredAllowed = new SparseBooleanArray();
+ @GuardedBy("mLock")
private long mLastCallbackAdjustmentTimeElapsed;
@GuardedBy("mLock")
private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();
@@ -250,6 +256,8 @@ public final class ConnectivityController extends RestrictingController implemen
private static final int MSG_ADJUST_CALLBACKS = 0;
private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
+ private static final int MSG_DATA_SAVER_TOGGLED = 2;
+ private static final int MSG_UID_POLICIES_CHANGED = 3;
private final Handler mHandler;
@@ -259,6 +267,7 @@ public final class ConnectivityController extends RestrictingController implemen
mHandler = new CcHandler(AppSchedulingModuleThread.get().getLooper());
mConnManager = mContext.getSystemService(ConnectivityManager.class);
+ mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
mFlexibilityController = flexibilityController;
@@ -266,6 +275,8 @@ public final class ConnectivityController extends RestrictingController implemen
// network changes against the active network for each UID with jobs.
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
mConnManager.registerNetworkCallback(request, mNetworkCallback);
+
+ mNetPolicyManager.registerListener(mNetPolicyListener);
}
@GuardedBy("mLock")
@@ -530,6 +541,7 @@ public final class ConnectivityController extends RestrictingController implemen
// All packages in the UID have been removed. It's safe to remove things based on
// UID alone.
mTrackedJobs.delete(uid);
+ mBackgroundMeteredAllowed.delete(uid);
UidStats uidStats = mUidStats.removeReturnOld(uid);
unregisterDefaultNetworkCallbackLocked(uid, sElapsedRealtimeClock.millis());
mSortedStats.remove(uidStats);
@@ -549,6 +561,12 @@ public final class ConnectivityController extends RestrictingController implemen
mUidStats.removeAt(u);
}
}
+ for (int u = mBackgroundMeteredAllowed.size() - 1; u >= 0; --u) {
+ final int uid = mBackgroundMeteredAllowed.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mBackgroundMeteredAllowed.removeAt(u);
+ }
+ }
postAdjustCallbacks();
}
@@ -666,6 +684,88 @@ public final class ConnectivityController extends RestrictingController implemen
return false;
}
+ private boolean isMeteredAllowed(@NonNull JobStatus jobStatus,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ // Network isn't metered. Usage is allowed. The rest of this method doesn't apply.
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
+ || networkCapabilities.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
+ return true;
+ }
+
+ final int uid = jobStatus.getSourceUid();
+ final int procState = mService.getUidProcState(uid);
+ final int capabilities = mService.getUidCapabilities(uid);
+ // Jobs don't raise the proc state to anything better than IMPORTANT_FOREGROUND.
+ // If the app is in a better state, see if it has the capability to use the metered network.
+ final boolean currentStateAllows = procState != ActivityManager.PROCESS_STATE_UNKNOWN
+ && procState < ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground(
+ procState, capabilities);
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid
+ + " current state allows metered network=" + currentStateAllows
+ + " procState=" + ActivityManager.procStateToString(procState)
+ + " capabilities=" + ActivityManager.getCapabilitiesSummary(capabilities));
+ }
+ if (currentStateAllows) {
+ return true;
+ }
+
+ if ((jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
+ final int expectedProcState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ final int mergedCapabilities = capabilities
+ | NetworkPolicyManager.getDefaultProcessNetworkCapabilities(expectedProcState);
+ final boolean wouldBeAllowed =
+ NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground(
+ expectedProcState, mergedCapabilities);
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid
+ + " willBeForeground flag allows metered network=" + wouldBeAllowed
+ + " capabilities="
+ + ActivityManager.getCapabilitiesSummary(mergedCapabilities));
+ }
+ if (wouldBeAllowed) {
+ return true;
+ }
+ }
+
+ if (jobStatus.shouldTreatAsUserInitiatedJob()) {
+ // Since the job is initiated by the user and will be visible to the user, it
+ // should be able to run on metered networks, similar to FGS.
+ // With user-initiated jobs, JobScheduler will request that the process
+ // run at IMPORTANT_FOREGROUND process state
+ // and get the USER_RESTRICTED_NETWORK process capability.
+ final int expectedProcState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ final int mergedCapabilities = capabilities
+ | ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK
+ | NetworkPolicyManager.getDefaultProcessNetworkCapabilities(expectedProcState);
+ final boolean wouldBeAllowed =
+ NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground(
+ expectedProcState, mergedCapabilities);
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid
+ + " UI job state allows metered network=" + wouldBeAllowed
+ + " capabilities=" + mergedCapabilities);
+ }
+ if (wouldBeAllowed) {
+ return true;
+ }
+ }
+
+ if (mBackgroundMeteredAllowed.indexOfKey(uid) >= 0) {
+ return mBackgroundMeteredAllowed.get(uid);
+ }
+
+ final boolean allowed =
+ mNetPolicyManager.getRestrictBackgroundStatus(uid)
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid + " allowed in data saver=" + allowed);
+ }
+ mBackgroundMeteredAllowed.put(uid, allowed);
+ return allowed;
+ }
+
/**
* Return the estimated amount of time this job will be transferring data,
* based on the current network speed.
@@ -859,6 +959,12 @@ public final class ConnectivityController extends RestrictingController implemen
// First, are we insane?
if (isInsane(jobStatus, network, capabilities, constants)) return false;
+ // User-initiated jobs might make NetworkPolicyManager open up network access for
+ // the whole UID. If network access is opened up just because of UI jobs, we want
+ // to make sure that non-UI jobs don't run during that time,
+ // so make sure the job can make use of the metered network at this time.
+ if (!isMeteredAllowed(jobStatus, capabilities)) return false;
+
// Second, is the network congested?
if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
@@ -1138,9 +1244,10 @@ public final class ConnectivityController extends RestrictingController implemen
// but it doesn't yet satisfy the requested constraints and the old network
// is still available and satisfies the constraints. Don't change the network
// given to the job for now and let it keep running. We will re-evaluate when
- // the capabilities or connection state of the either network change.
+ // the capabilities or connection state of either network change.
if (DEBUG) {
- Slog.i(TAG, "Not reassigning network for running job " + jobStatus);
+ Slog.i(TAG, "Not reassigning network from " + jobStatus.network
+ + " to " + network + " for running job " + jobStatus);
}
return false;
}
@@ -1389,6 +1496,26 @@ public final class ConnectivityController extends RestrictingController implemen
}
};
+ private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
+ @Override
+ public void onRestrictBackgroundChanged(boolean restrictBackground) {
+ if (DEBUG) {
+ Slog.v(TAG, "onRestrictBackgroundChanged: " + restrictBackground);
+ }
+ mHandler.obtainMessage(MSG_DATA_SAVER_TOGGLED).sendToTarget();
+ }
+
+ @Override
+ public void onUidPoliciesChanged(int uid, int uidPolicies) {
+ if (DEBUG) {
+ Slog.v(TAG, "onUidPoliciesChanged: " + uid);
+ }
+ mHandler.obtainMessage(MSG_UID_POLICIES_CHANGED,
+ uid, mNetPolicyManager.getRestrictBackgroundStatus(uid))
+ .sendToTarget();
+ }
+ };
+
private class CcHandler extends Handler {
CcHandler(Looper looper) {
super(looper);
@@ -1410,6 +1537,27 @@ public final class ConnectivityController extends RestrictingController implemen
updateAllTrackedJobsLocked(allowThrottle);
}
break;
+
+ case MSG_DATA_SAVER_TOGGLED:
+ removeMessages(MSG_DATA_SAVER_TOGGLED);
+ synchronized (mLock) {
+ mBackgroundMeteredAllowed.clear();
+ updateTrackedJobsLocked(-1, null);
+ }
+ break;
+
+ case MSG_UID_POLICIES_CHANGED:
+ final int uid = msg.arg1;
+ final boolean newAllowed =
+ msg.arg2 != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+ synchronized (mLock) {
+ final boolean oldAllowed = mBackgroundMeteredAllowed.get(uid);
+ if (oldAllowed != newAllowed) {
+ mBackgroundMeteredAllowed.put(uid, newAllowed);
+ updateTrackedJobsLocked(uid, null);
+ }
+ }
+ break;
}
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index e7d139dc4924..76f17218f1bc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -64,8 +64,6 @@ package android {
field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
field public static final String BODY_SENSORS_BACKGROUND = "android.permission.BODY_SENSORS_BACKGROUND";
- field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
- field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED";
field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
field public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
@@ -5024,7 +5022,6 @@ package android.app {
field public static final String OPSTR_ADD_VOICEMAIL = "android:add_voicemail";
field public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls";
field public static final String OPSTR_BODY_SENSORS = "android:body_sensors";
- field public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE = "android:body_sensors_wrist_temperature";
field public static final String OPSTR_CALL_PHONE = "android:call_phone";
field public static final String OPSTR_CAMERA = "android:camera";
field public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -13029,7 +13026,7 @@ package android.content.pm {
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
- field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
@@ -60403,6 +60400,7 @@ package android.widget {
method public void setLineBreakStyle(int);
method public void setLineBreakWordStyle(int);
method public void setLineHeight(@IntRange(from=0) @Px int);
+ method public void setLineHeight(int, @FloatRange(from=0) float);
method public void setLineSpacing(float, float);
method public void setLines(int);
method public final void setLinkTextColor(@ColorInt int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 58f78aa4fc15..c1f6219025f9 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -18,6 +18,7 @@ package android.app {
public class ActivityManager {
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
+ method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int[] getUidFrozenState(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void registerUidFrozenStateChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b21028464426..fbc69e34a644 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -33,7 +33,6 @@ package android {
field public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY";
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
- field public static final String ALLOWLISTED_WRITE_DEVICE_CONFIG = "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG";
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
field public static final String ALLOW_SLIPPERY_TOUCHES = "android.permission.ALLOW_SLIPPERY_TOUCHES";
@@ -380,6 +379,7 @@ package android {
field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
+ field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1a8ebb15663b..ecbf885bf47d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -5,10 +5,11 @@ package android {
field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
- field public static final String ALLOWLISTED_WRITE_DEVICE_CONFIG = "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
+ field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
+ field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
@@ -55,6 +56,7 @@ package android {
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+ field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
@@ -131,11 +133,13 @@ package android.app {
method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getDisplayIdsForStartingVisibleBackgroundUsers();
method public long getTotalRam();
+ method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int[] getUidFrozenState(@NonNull int[]);
method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int);
method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int);
method public void holdLock(android.os.IBinder, int);
method public static boolean isHighEndGfx();
method public void notifySystemPropertiesChanged();
+ method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void registerUidFrozenStateChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
method public static void resumeAppSwitches() throws android.os.RemoteException;
@@ -143,12 +147,13 @@ package android.app {
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL
field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
- field @Deprecated public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
+ field public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 32; // 0x20
field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
field public static final int PROCESS_STATE_TOP = 2; // 0x2
field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -166,6 +171,12 @@ package android.app {
method @Nullable public String getIconResourcePackage();
}
+ public static interface ActivityManager.UidFrozenStateChangedCallback {
+ method public void onUidFrozenStateChanged(@NonNull int[], @NonNull int[]);
+ field public static final int UID_FROZEN_STATE_FROZEN = 1; // 0x1
+ field public static final int UID_FROZEN_STATE_UNFROZEN = 2; // 0x2
+ }
+
public class ActivityOptions extends android.app.ComponentOptions {
method public boolean isEligibleForLegacyPermissionPrompt();
method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
@@ -2607,7 +2618,6 @@ package android.provider {
field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
field public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog";
- field public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled";
field public static final String USER_DISABLED_HDR_FORMATS = "user_disabled_hdr_formats";
field public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate";
field public static final String USER_PREFERRED_RESOLUTION_HEIGHT = "user_preferred_resolution_height";
@@ -2639,6 +2649,8 @@ package android.provider {
field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option";
field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
field public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
+ field public static final int STYLUS_HANDWRITING_DEFAULT_VALUE = 1; // 0x1
+ field public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled";
field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 5d69f8b80799..ead238f75ba4 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1118,10 +1118,6 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
- if (playBackwards == mResumed && mSelfPulse == !mSuppressSelfPulseRequested && mStarted) {
- // already started
- return;
- }
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 929c07bc1dc5..813e32a81983 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -256,6 +256,7 @@ public class ActivityManager {
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public interface UidFrozenStateChangedCallback {
/**
* Indicates that the UID was frozen.
@@ -263,6 +264,7 @@ public class ActivityManager {
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
int UID_FROZEN_STATE_FROZEN = 1;
/**
@@ -271,6 +273,7 @@ public class ActivityManager {
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
int UID_FROZEN_STATE_UNFROZEN = 2;
/**
@@ -296,6 +299,7 @@ public class ActivityManager {
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
void onUidFrozenStateChanged(@NonNull int[] uids,
@NonNull @UidFrozenState int[] frozenStates);
}
@@ -315,6 +319,7 @@ public class ActivityManager {
*/
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public void registerUidFrozenStateChangedCallback(
@NonNull Executor executor,
@NonNull UidFrozenStateChangedCallback callback) {
@@ -346,6 +351,7 @@ public class ActivityManager {
*/
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public void unregisterUidFrozenStateChangedCallback(
@NonNull UidFrozenStateChangedCallback callback) {
Preconditions.checkNotNull(callback, "callback cannot be null");
@@ -363,6 +369,30 @@ public class ActivityManager {
}
/**
+ * Query the frozen state of a list of UIDs.
+ *
+ * @param uids the array of UIDs which the client would like to know the frozen state of.
+ * @return An array containing the frozen state for each requested UID, by index. Will be set
+ * to {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
+ * if the UID is frozen. If the UID is not frozen or not found,
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN}
+ * will be set.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public @NonNull @UidFrozenStateChangedCallback.UidFrozenState
+ int[] getUidFrozenState(@NonNull int[] uids) {
+ try {
+ return getService().getUidFrozenState(uids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
* <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
* uninstalled in lieu of the declaring one. The package named here must be
@@ -764,6 +794,7 @@ public class ActivityManager {
PROCESS_CAPABILITY_FOREGROUND_MICROPHONE,
PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK,
PROCESS_CAPABILITY_BFSL,
+ PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProcessCapability {}
@@ -885,14 +916,6 @@ public class ActivityManager {
/** @hide Process can access network despite any power saving restrictions */
@TestApi
public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 1 << 3;
- /**
- * @hide
- * @deprecated Use {@link #PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK} instead.
- */
- @TestApi
- @Deprecated
- public static final int PROCESS_CAPABILITY_NETWORK =
- PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
/**
* Flag used to indicate whether an app is allowed to start a foreground service from the
@@ -914,6 +937,13 @@ public class ActivityManager {
public static final int PROCESS_CAPABILITY_BFSL = 1 << 4;
/**
+ * @hide
+ * Process can access network at a high enough proc state despite any user restrictions.
+ */
+ @TestApi
+ public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 1 << 5;
+
+ /**
* @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
*
* Don't expose it as TestApi -- we may add new capabilities any time, which could
@@ -923,7 +953,8 @@ public class ActivityManager {
| PROCESS_CAPABILITY_FOREGROUND_CAMERA
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
| PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
- | PROCESS_CAPABILITY_BFSL;
+ | PROCESS_CAPABILITY_BFSL
+ | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
/**
* All implicit capabilities. There are capabilities that process automatically have.
@@ -943,6 +974,7 @@ public class ActivityManager {
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
pw.print((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-');
pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
}
/** @hide */
@@ -952,6 +984,7 @@ public class ActivityManager {
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
sb.append((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-');
sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
}
/**
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index feb9b4f2664d..d73f0cca9a4e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2539,7 +2539,8 @@ public class ActivityOptions extends ComponentOptions {
public String toString() {
return "ActivityOptions(" + hashCode() + "), mPackageName=" + mPackageName
+ ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY="
- + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight;
+ + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight + ", mLaunchDisplayId="
+ + mLaunchDisplayId;
}
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 265b56418d4b..b48a8fb73832 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2188,7 +2188,10 @@ public class AppOpsManager {
public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
"android:capture_consentless_bugreport_on_userdebug_build";
- /** Access to wrist temperature body sensors. */
+ /**
+ * Access to wrist temperature body sensors.
+ * @hide
+ */
public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE =
"android:body_sensors_wrist_temperature";
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 97d45623d3da..91eb4c44cda5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -882,4 +882,6 @@ interface IActivityManager {
void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
+ int[] getUidFrozenState(in int[] uids);
}
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
index 234da8f36e96..cfdb426d6026 100644
--- a/core/java/android/app/IUserSwitchObserver.aidl
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -20,6 +20,7 @@ import android.os.IRemoteCallback;
/** {@hide} */
oneway interface IUserSwitchObserver {
+ void onBeforeUserSwitching(int newUserId);
void onUserSwitching(int newUserId, IRemoteCallback reply);
void onUserSwitchComplete(int newUserId);
void onForegroundProfileSwitch(int newProfileId);
diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java
index 6abc4f09ba38..727799a1f948 100644
--- a/core/java/android/app/UserSwitchObserver.java
+++ b/core/java/android/app/UserSwitchObserver.java
@@ -30,6 +30,9 @@ public class UserSwitchObserver extends IUserSwitchObserver.Stub {
}
@Override
+ public void onBeforeUserSwitching(int newUserId) throws RemoteException {}
+
+ @Override
public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException {
if (reply != null) {
reply.sendResult(null);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 924a7c659b08..bad6c77a17f3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -15775,7 +15775,7 @@ public class DevicePolicyManager {
throwIfParentInstance("setApplicationExemptions");
if (mService != null) {
try {
- mService.setApplicationExemptions(packageName,
+ mService.setApplicationExemptions(mContext.getPackageName(), packageName,
ArrayUtils.convertToIntArray(new ArraySet<>(exemptions)));
} catch (ServiceSpecificException e) {
switch (e.errorCode) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e202ac2c9245..8d508c0fb79d 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -591,7 +591,7 @@ interface IDevicePolicyManager {
List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
- void setApplicationExemptions(String packageName, in int[]exemptions);
+ void setApplicationExemptions(String callerPackage, String packageName, in int[]exemptions);
int[] getApplicationExemptions(String packageName);
void setMtePolicy(int flag, String callerPackageName);
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 8a22ce3a75f8..107f1078b11e 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -85,7 +85,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*
* @hide
*/
- public static final boolean DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS = false;
+ public static final boolean DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS = true;
private final Context mContext;
private final Handler mHandler;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3b2ea785988e..2b73afcd740f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -322,6 +322,7 @@ public abstract class Context {
BIND_EXTERNAL_SERVICE_LONG,
// Make sure no flag uses the sign bit (most significant bit) of the long integer,
// to avoid future confusion.
+ BIND_BYPASS_USER_NETWORK_RESTRICTIONS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlagsLongBits {}
@@ -688,6 +689,16 @@ public abstract class Context {
public static final long BIND_EXTERNAL_SERVICE_LONG = 1L << 62;
/**
+ * Flag for {@link #bindService}: allow the process hosting the target service to gain
+ * {@link ActivityManager#PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK}, which allows it be able
+ * to access network regardless of any user restrictions.
+ *
+ * @hide
+ */
+ public static final long BIND_BYPASS_USER_NETWORK_RESTRICTIONS = 0x1_0000_0000L;
+
+
+ /**
* These bind flags reduce the strength of the binding such that we shouldn't
* consider it as pulling the process up to the level of the one that is bound to it.
* @hide
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index df8da246c976..58b0571653f1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11356,12 +11356,15 @@ public class Intent implements Parcelable, Cloneable {
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
+ toString(b);
+ return b.toString();
+ }
+ /** @hide */
+ public void toString(@NonNull StringBuilder b) {
b.append("Intent { ");
toShortString(b, true, true, true, false);
b.append(" }");
-
- return b.toString();
}
/** @hide */
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 1a3c3d97634c..7e0954a55560 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -332,7 +332,6 @@ public class ServiceInfo extends ComponentInfo
* permissions:
* {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
* {@link android.Manifest.permission#BODY_SENSORS},
- * {@link android.Manifest.permission#BODY_SENSORS_WRIST_TEMPERATURE},
* {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
*/
@RequiresPermission(
@@ -342,7 +341,6 @@ public class ServiceInfo extends ComponentInfo
anyOf = {
Manifest.permission.ACTIVITY_RECOGNITION,
Manifest.permission.BODY_SENSORS,
- Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE,
Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
}
)
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 81d6ba93cfe9..ccc39b6080d7 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -51,6 +51,7 @@ import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
@@ -486,8 +487,22 @@ public class Camera {
boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
ActivityThread.currentApplication().getApplicationContext());
+ boolean forceSlowJpegMode = shouldForceSlowJpegMode();
return native_setup(new WeakReference<Camera>(this), cameraId,
- ActivityThread.currentOpPackageName(), overrideToPortrait);
+ ActivityThread.currentOpPackageName(), overrideToPortrait, forceSlowJpegMode);
+ }
+
+ private boolean shouldForceSlowJpegMode() {
+ Context applicationContext = ActivityThread.currentApplication().getApplicationContext();
+ String[] slowJpegPackageNames = applicationContext.getResources().getStringArray(
+ R.array.config_forceSlowJpegModeList);
+ String callingPackageName = applicationContext.getPackageName();
+ for (String packageName : slowJpegPackageNames) {
+ if (TextUtils.equals(packageName, callingPackageName)) {
+ return true;
+ }
+ }
+ return false;
}
/** used by Camera#open, Camera#open(int) */
@@ -558,7 +573,7 @@ public class Camera {
@UnsupportedAppUsage
private native int native_setup(Object cameraThis, int cameraId, String packageName,
- boolean overrideToPortrait);
+ boolean overrideToPortrait, boolean forceSlowJpegMode);
private native final void native_release();
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 144b1de148b4..73dd50945289 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -152,21 +152,8 @@ public final class CameraManager {
mContext.checkSelfPermission(CAMERA_OPEN_CLOSE_LISTENER_PERMISSION) ==
PackageManager.PERMISSION_GRANTED;
}
-
- mFoldStateListener = new FoldStateListener(context);
- try {
- context.getSystemService(DeviceStateManager.class).registerCallback(
- new HandlerExecutor(CameraManagerGlobal.get().getDeviceStateHandler()),
- mFoldStateListener);
- } catch (IllegalStateException e) {
- Log.v(TAG, "Failed to register device state listener!");
- Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
- mFoldStateListener = null;
- }
}
- private FoldStateListener mFoldStateListener;
-
/**
* @hide
*/
@@ -228,12 +215,7 @@ public final class CameraManager {
* @hide
*/
public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) {
- synchronized (mLock) {
- DeviceStateListener listener = chars.getDeviceStateListener();
- if (mFoldStateListener != null) {
- mFoldStateListener.addDeviceStateListener(listener);
- }
- }
+ CameraManagerGlobal.get().registerDeviceStateListener(chars, mContext);
}
/**
@@ -627,6 +609,21 @@ public final class CameraManager {
}
/**
+ * <p>Query the capabilities of a camera device. These capabilities are
+ * immutable for a given camera.</p>
+ *
+ * <p>The value of {@link CameraCharacteristics.SENSOR_ORIENTATION} will change for landscape
+ * cameras depending on whether overrideToPortrait is enabled. If enabled, these cameras will
+ * appear to be portrait orientation instead, provided that the override is supported by the
+ * camera device. Only devices that can be opened by {@link #openCamera} will report a changed
+ * {@link CameraCharacteristics.SENSOR_ORIENTATION}.</p>
+ *
+ * @param cameraId The id of the camera device to query. This could be either a standalone
+ * camera ID which can be directly opened by {@link #openCamera}, or a physical camera ID that
+ * can only used as part of a logical multi-camera.
+ * @param overrideToPortrait Whether to apply the landscape to portrait override.
+ * @return The properties of the given camera
+ *
* @hide
*/
@TestApi
@@ -1781,6 +1778,7 @@ public final class CameraManager {
private HandlerThread mDeviceStateHandlerThread;
private Handler mDeviceStateHandler;
+ private FoldStateListener mFoldStateListener;
// Singleton, don't allow construction
private CameraManagerGlobal() { }
@@ -1795,7 +1793,8 @@ public final class CameraManager {
return gCameraManager;
}
- public Handler getDeviceStateHandler() {
+ public void registerDeviceStateListener(@NonNull CameraCharacteristics chars,
+ @NonNull Context ctx) {
synchronized(mLock) {
if (mDeviceStateHandlerThread == null) {
mDeviceStateHandlerThread = new HandlerThread(TAG);
@@ -1803,7 +1802,20 @@ public final class CameraManager {
mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper());
}
- return mDeviceStateHandler;
+ if (mFoldStateListener == null) {
+ mFoldStateListener = new FoldStateListener(ctx);
+ try {
+ ctx.getSystemService(DeviceStateManager.class).registerCallback(
+ new HandlerExecutor(mDeviceStateHandler), mFoldStateListener);
+ } catch (IllegalStateException e) {
+ Log.v(TAG, "Failed to register device state listener!");
+ Log.v(TAG, "Device state dependent characteristics updates will not be" +
+ "functional!");
+ return;
+ }
+ }
+
+ mFoldStateListener.addDeviceStateListener(chars.getDeviceStateListener());
}
}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 104a8b2095d8..efed6883a114 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -780,11 +780,11 @@ public class NetworkPolicyManager {
case ActivityManager.PROCESS_STATE_PERSISTENT:
case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
case ActivityManager.PROCESS_STATE_TOP:
- return ActivityManager.PROCESS_CAPABILITY_ALL;
case ActivityManager.PROCESS_STATE_BOUND_TOP:
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
- return ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+ return ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
+ | ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
default:
return ActivityManager.PROCESS_CAPABILITY_NONE;
}
@@ -826,13 +826,18 @@ public class NetworkPolicyManager {
if (uidState == null) {
return false;
}
- return isProcStateAllowedWhileOnRestrictBackground(uidState.procState);
+ return isProcStateAllowedWhileOnRestrictBackground(uidState.procState, uidState.capability);
}
/** @hide */
- public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
- // Data saver and bg policy restrictions will only take procstate into account.
- return procState <= FOREGROUND_THRESHOLD_STATE;
+ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState,
+ @ProcessCapability int capabilities) {
+ return procState <= FOREGROUND_THRESHOLD_STATE
+ // This is meant to be a user-initiated job, and therefore gets similar network
+ // access to FGS.
+ || (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && (capabilities
+ & ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0);
}
/** @hide */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7ae280fd7d90..c473d3f81823 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7113,6 +7113,28 @@ public final class Settings {
"input_method_selector_visibility";
/**
+ * Toggle for enabling stylus handwriting. When enabled, current Input method receives
+ * stylus {@link MotionEvent}s if an {@link Editor} is focused.
+ *
+ * @see #STYLUS_HANDWRITING_DEFAULT_VALUE
+ * @hide
+ */
+ @TestApi
+ @Readable
+ @SuppressLint("NoSettingsProvider")
+ public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled";
+
+ /**
+ * Default value for {@link #STYLUS_HANDWRITING_ENABLED}.
+ *
+ * @hide
+ */
+ @TestApi
+ @Readable
+ @SuppressLint("NoSettingsProvider")
+ public static final int STYLUS_HANDWRITING_DEFAULT_VALUE = 1;
+
+ /**
* The currently selected voice interaction service flattened ComponentName.
* @hide
*/
@@ -16482,17 +16504,6 @@ public final class Settings {
public static final String AUTOFILL_MAX_VISIBLE_DATASETS = "autofill_max_visible_datasets";
/**
- * Toggle for enabling stylus handwriting. When enabled, current Input method receives
- * stylus {@link MotionEvent}s if an {@link Editor} is focused.
- *
- * @hide
- */
- @TestApi
- @Readable
- @SuppressLint("NoSettingsProvider")
- public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled";
-
- /**
* Indicates whether a stylus has ever been used on the device.
*
* @hide
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 9120b8a6b88c..d2a4a660452c 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -167,7 +167,8 @@ public final class CredentialProviderInfoFactory {
Slog.w(TAG, "Context is null in isSystemProviderWithValidPermission");
return false;
}
- return PermissionUtils.hasPermission(context, serviceInfo.packageName,
+ return PermissionUtils.isSystemApp(context, serviceInfo.packageName)
+ && PermissionUtils.hasPermission(context, serviceInfo.packageName,
Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE);
}
diff --git a/core/java/android/service/credentials/PermissionUtils.java b/core/java/android/service/credentials/PermissionUtils.java
index c8bb202c35f7..d958111f2e0e 100644
--- a/core/java/android/service/credentials/PermissionUtils.java
+++ b/core/java/android/service/credentials/PermissionUtils.java
@@ -30,16 +30,19 @@ public class PermissionUtils {
/** Checks whether the given package name hold the given permission **/
public static boolean hasPermission(Context context, String packageName, String permission) {
+ return context.getPackageManager().checkPermission(permission, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /** Checks whether the given package name is a system app on the device **/
+ public static boolean isSystemApp(Context context, String packageName) {
try {
ApplicationInfo appInfo =
context.getPackageManager()
- .getApplicationInfo(
- packageName,
+ .getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(
PackageManager.MATCH_SYSTEM_ONLY));
- if (appInfo != null
- && context.checkPermission(permission, /* pid= */ -1, appInfo.uid)
- == PackageManager.PERMISSION_GRANTED) {
+ if (appInfo != null) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index bc514b091409..c3295088ef11 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -234,8 +234,8 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "true");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
- DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "false");
- DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "false");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
+ DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 46ae3ea21890..f5e4da86bfea 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -66,6 +66,7 @@ import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import android.view.animation.Transformation;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
import android.view.autofill.Helper;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
@@ -3709,6 +3710,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return children;
}
+ private AutofillManager getAutofillManager() {
+ return mContext.getSystemService(AutofillManager.class);
+ }
+
+ private boolean shouldIncludeAllChildrenViewWithAutofillTypeNotNone(AutofillManager afm) {
+ if (afm == null) return false;
+ return afm.shouldIncludeAllChildrenViewsWithAutofillTypeNotNoneInAssistStructure();
+ }
+
+ private boolean shouldIncludeAllChildrenViews(AutofillManager afm){
+ if (afm == null) return false;
+ return afm.shouldIncludeAllChildrenViewInAssistStructure();
+ }
+
/** @hide */
private void populateChildrenForAutofill(ArrayList<View> list, @AutofillFlags int flags) {
final int childrenCount = mChildrenCount;
@@ -3718,6 +3733,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
+ final AutofillManager afm = getAutofillManager();
for (int i = 0; i < childrenCount; i++) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = (preorderedList == null)
@@ -3725,7 +3741,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if ((flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
|| child.isImportantForAutofill()
|| (child.isMatchingAutofillableHeuristics()
- && !child.isActivityDeniedForAutofillForUnimportantView())) {
+ && !child.isActivityDeniedForAutofillForUnimportantView())
+ || (shouldIncludeAllChildrenViewWithAutofillTypeNotNone(afm)
+ && child.getAutofillType() != AUTOFILL_TYPE_NONE)
+ || shouldIncludeAllChildrenViews(afm)){
list.add(child);
} else if (child instanceof ViewGroup) {
((ViewGroup) child).populateChildrenForAutofill(list, flags);
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index e51eff42fed5..4aa612c526fe 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -193,6 +193,24 @@ public class AutofillFeatureFlags {
public static final String DEVICE_CONFIG_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES =
"should_enable_autofill_on_all_view_types";
+ /**
+ * Whether include all autofill type not none views in assist structure
+ *
+ * @hide
+ */
+ public static final String
+ DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE =
+ "include_all_autofill_type_not_none_views_in_assist_structure";
+
+ /**
+ * Whether include all views in assist structure
+ *
+ * @hide
+ */
+ public static final String
+ DEVICE_CONFIG_INCLUDE_ALL_VIEWS_IN_ASSIST_STRUCTURE =
+ "include_all_views_in_assist_structure";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -398,6 +416,28 @@ public class AutofillFeatureFlags {
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST, "");
}
+ /**
+ * Whether include all views that have autofill type not none in assist structure.
+ *
+ * @hide
+ */
+ public static boolean shouldIncludeAllViewsAutofillTypeNotNoneInAssistStructrue() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE, false);
+ }
+
+ /**
+ * Whether include all views in assist structure.
+ *
+ * @hide
+ */
+ public static boolean shouldIncludeAllChildrenViewInAssistStructure() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_INCLUDE_ALL_VIEWS_IN_ASSIST_STRUCTURE, false);
+ }
+
// START AUTOFILL PCC CLASSIFICATION FUNCTIONS
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index cc8ab1072083..1ef7afc8615b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -707,6 +707,12 @@ public final class AutofillManager {
// An allowed activity set read from device config
private Set<String> mAllowedActivitySet = new ArraySet<>();
+ // Indicate whether should include all view with autofill type not none in assist structure
+ private boolean mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure;
+
+ // Indicate whether should include all view in assist structure
+ private boolean mShouldIncludeAllChildrenViewInAssistStructure;
+
// Indicates whether called the showAutofillDialog() method.
private boolean mShowAutofillDialogCalled = false;
@@ -913,6 +919,12 @@ public final class AutofillManager {
mAllowedActivitySet = getDeniedOrAllowedActivitySetFromString(
allowlistString, packageName);
}
+
+ mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure
+ = AutofillFeatureFlags.shouldIncludeAllViewsAutofillTypeNotNoneInAssistStructrue();
+
+ mShouldIncludeAllChildrenViewInAssistStructure
+ = AutofillFeatureFlags.shouldIncludeAllChildrenViewInAssistStructure();
}
/**
@@ -963,6 +975,20 @@ public final class AutofillManager {
}
/**
+ * @hide
+ */
+ public boolean shouldIncludeAllChildrenViewsWithAutofillTypeNotNoneInAssistStructure() {
+ return mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean shouldIncludeAllChildrenViewInAssistStructure() {
+ return mShouldIncludeAllChildrenViewInAssistStructure;
+ }
+
+ /**
* Get the denied or allowed activitiy names under specified package from the list string and
* set it in fields accordingly
*
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index d84acc03826b..ce2c18080b91 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -508,6 +508,7 @@ final class IInputMethodManagerGlobalInvoker {
@AnyThread
static void prepareStylusHandwritingDelegation(
@NonNull IInputMethodClient client,
+ @UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName) {
final IInputMethodManager service = getService();
@@ -516,7 +517,7 @@ final class IInputMethodManagerGlobalInvoker {
}
try {
service.prepareStylusHandwritingDelegation(
- client, delegatePackageName, delegatorPackageName);
+ client, userId, delegatePackageName, delegatorPackageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -525,6 +526,7 @@ final class IInputMethodManagerGlobalInvoker {
@AnyThread
static boolean acceptStylusHandwritingDelegation(
@NonNull IInputMethodClient client,
+ @UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName) {
final IInputMethodManager service = getService();
@@ -533,7 +535,7 @@ final class IInputMethodManagerGlobalInvoker {
}
try {
return service.acceptStylusHandwritingDelegation(
- client, delegatePackageName, delegatorPackageName);
+ client, userId, delegatePackageName, delegatorPackageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 36d2b8a89779..515b95cd951d 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1553,9 +1553,7 @@ public final class InputMethodManager {
if (fallbackContext == null) {
return false;
}
- if (!isStylusHandwritingEnabled(fallbackContext)) {
- return false;
- }
+
return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
}
@@ -2244,11 +2242,6 @@ public final class InputMethodManager {
}
boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);
- if (!isStylusHandwritingEnabled(view.getContext())) {
- Log.w(TAG, "Stylus handwriting pref is disabled. "
- + "Ignoring calls to start stylus handwriting.");
- return false;
- }
checkFocus();
synchronized (mH) {
@@ -2264,7 +2257,8 @@ public final class InputMethodManager {
}
if (useDelegation) {
return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
- mClient, view.getContext().getOpPackageName(), delegatorPackageName);
+ mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
+ delegatorPackageName);
} else {
IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
}
@@ -2272,15 +2266,6 @@ public final class InputMethodManager {
}
}
- private boolean isStylusHandwritingEnabled(@NonNull Context context) {
- if (Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
- Log.d(TAG, "Stylus handwriting pref is disabled.");
- return false;
- }
- return true;
- }
-
/**
* Prepares delegation of starting stylus handwriting session to a different editor in same
* or different window than the view on which initial handwriting stroke was detected.
@@ -2344,13 +2329,9 @@ public final class InputMethodManager {
fallbackImm.prepareStylusHandwritingDelegation(delegatorView, delegatePackageName);
}
- if (!isStylusHandwritingEnabled(delegatorView.getContext())) {
- Log.w(TAG, "Stylus handwriting pref is disabled. "
- + "Ignoring prepareStylusHandwritingDelegation().");
- return;
- }
IInputMethodManagerGlobalInvoker.prepareStylusHandwritingDelegation(
mClient,
+ UserHandle.myUserId(),
delegatePackageName,
delegatorView.getContext().getOpPackageName());
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fd80981fe4b8..d56a06fbd127 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4604,7 +4604,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
+ @NonNull
+ private DisplayMetrics getDisplayMetricsOrSystem() {
Context c = getContext();
Resources r;
@@ -4614,8 +4615,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
r = c.getResources();
}
+ return r.getDisplayMetrics();
+ }
+
+ private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
mTextSizeUnit = unit;
- setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
+ setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()),
shouldRequestLayout);
}
@@ -6197,10 +6202,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
- Preconditions.checkArgumentNonnegative(lineHeight);
+ setLineHeightPx(lineHeight);
+ }
+
+ private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) {
+ Preconditions.checkArgumentNonnegative((int) lineHeight);
final int fontHeight = getPaint().getFontMetricsInt(null);
// Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
+ // TODO(b/274974975): should this also check if lineSpacing needs to change?
if (lineHeight != fontHeight) {
// Set lineSpacingExtra by the difference of lineSpacing with lineHeight
setLineSpacing(lineHeight - fontHeight, 1f);
@@ -6208,6 +6218,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets an explicit line height to a given unit and value for this TextView. This is equivalent
+ * to the vertical distance between subsequent baselines in the TextView. See {@link
+ * TypedValue} for the possible dimension units.
+ *
+ * @param unit The desired dimension unit. SP units are strongly recommended so that line height
+ * stays proportional to the text size when fonts are scaled up for accessibility.
+ * @param lineHeight The desired line height in the given units.
+ *
+ * @see #setLineSpacing(float, float)
+ * @see #getLineSpacingExtra()
+ *
+ * @attr ref android.R.styleable#TextView_lineHeight
+ */
+ @android.view.RemotableViewMethod
+ public void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight) {
+ setLineHeightPx(
+ TypedValue.applyDimension(unit, lineHeight, getDisplayMetricsOrSystem()));
+ }
+
+ /**
* Set Highlights
*
* @param highlights A highlight object. Call with null for reset.
@@ -11467,7 +11497,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
changed = true;
}
- if (requestRectWithoutFocus && isFocused()) {
+ if (requestRectWithoutFocus || isFocused()) {
// This offsets because getInterestingRect() is in terms of viewport coordinates, but
// requestRectangleOnScreen() is in terms of content coordinates.
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index 2f6091bc3266..a0e29347d07f 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -18,6 +18,8 @@ package com.android.internal.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.SystemClock;
@@ -98,18 +100,41 @@ public class TimeoutRecord {
/** Record for a broadcast receiver timeout. */
@NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+ @Nullable String packageName, @Nullable String className) {
+ final Intent logIntent;
+ if (packageName != null) {
+ if (className != null) {
+ logIntent = new Intent(intent);
+ logIntent.setComponent(new ComponentName(packageName, className));
+ } else {
+ logIntent = new Intent(intent);
+ logIntent.setPackage(packageName);
+ }
+ } else {
+ logIntent = intent;
+ }
+ return forBroadcastReceiver(logIntent);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
- String reason = "Broadcast of " + intent.toString();
- return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+ final StringBuilder reason = new StringBuilder("Broadcast of ");
+ intent.toString(reason);
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason.toString());
}
/** Record for a broadcast receiver timeout. */
@NonNull
public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
long timeoutDurationMs) {
- String reason = "Broadcast of " + intent.toString() + ", waited " + timeoutDurationMs
- + "ms";
- return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+ final StringBuilder reason = new StringBuilder("Broadcast of ");
+ intent.toString(reason);
+ reason.append(", waited ");
+ reason.append(timeoutDurationMs);
+ reason.append("ms");
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason.toString());
}
/** Record for an input dispatch no focused window timeout */
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9a4610e8c0a1..549169388e45 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -150,12 +150,13 @@ interface IInputMethodManager {
/** Prepares delegation of starting stylus handwriting session to a different editor **/
void prepareStylusHandwritingDelegation(in IInputMethodClient client,
+ in int userId,
in String delegatePackageName,
in String delegatorPackageName);
/** Accepts and starts a stylus handwriting session for the delegate view **/
boolean acceptStylusHandwritingDelegation(in IInputMethodClient client,
- in String delegatePackageName, in String delegatorPackageName);
+ in int userId, in String delegatePackageName, in String delegatorPackageName);
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index a8abe50a9755..2a670e865ced 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -556,7 +556,8 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jin
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint cameraId, jstring clientPackageName,
- jboolean overrideToPortrait) {
+ jboolean overrideToPortrait,
+ jboolean forceSlowJpegMode) {
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
env->GetStringChars(clientPackageName, NULL));
@@ -568,7 +569,7 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj
int targetSdkVersion = android_get_application_target_sdk_version();
sp<Camera> camera =
Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
- targetSdkVersion, overrideToPortrait);
+ targetSdkVersion, overrideToPortrait, forceSlowJpegMode);
if (camera == NULL) {
return -EACCES;
}
@@ -1054,7 +1055,7 @@ static const JNINativeMethod camMethods[] = {
{"getNumberOfCameras", "()I", (void *)android_hardware_Camera_getNumberOfCameras},
{"_getCameraInfo", "(IZLandroid/hardware/Camera$CameraInfo;)V",
(void *)android_hardware_Camera_getCameraInfo},
- {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;Z)I",
+ {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;ZZ)I",
(void *)android_hardware_Camera_native_setup},
{"native_release", "()V", (void *)android_hardware_Camera_release},
{"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 49f47c56e6a6..2e9f1790a4a5 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -316,8 +316,9 @@ static void pointerPropertiesToNative(JNIEnv* env, jobject pointerPropertiesObj,
outPointerProperties->clear();
outPointerProperties->id = env->GetIntField(pointerPropertiesObj,
gPointerPropertiesClassInfo.id);
- outPointerProperties->toolType = env->GetIntField(pointerPropertiesObj,
+ const int32_t toolType = env->GetIntField(pointerPropertiesObj,
gPointerPropertiesClassInfo.toolType);
+ outPointerProperties->toolType = static_cast<ToolType>(toolType);
}
static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* pointerProperties,
@@ -325,7 +326,7 @@ static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* po
env->SetIntField(outPointerPropertiesObj, gPointerPropertiesClassInfo.id,
pointerProperties->id);
env->SetIntField(outPointerPropertiesObj, gPointerPropertiesClassInfo.toolType,
- pointerProperties->toolType);
+ static_cast<int32_t>(pointerProperties->toolType));
}
@@ -535,7 +536,7 @@ static jint android_view_MotionEvent_nativeGetToolType(JNIEnv* env, jclass clazz
if (!validatePointerIndex(env, pointerIndex, *event)) {
return -1;
}
- return event->getToolType(pointerIndex);
+ return static_cast<jint>(event->getToolType(pointerIndex));
}
static jlong android_view_MotionEvent_nativeGetEventTimeNanos(JNIEnv* env, jclass clazz,
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index d1f7b63b57ea..e6c8557a8c50 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -390,6 +390,7 @@ message ActivityRecordProto {
optional bool enable_recents_screenshot = 35;
optional int32 last_drop_input_mode = 36;
optional int32 override_orientation = 37 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"];
+ optional bool should_send_compat_fake_focus = 38;
}
/* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 393e7785b446..78d39236b392 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1746,9 +1746,11 @@
android:protectionLevel="dangerous"
android:permissionFlags="hardRestricted" />
- <!-- Allows an application to access wrist temperature data from the watch sensors.
+ <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
<p class="note"><strong>Note: </strong> This permission is for Wear OS only.
- <p>Protection level: dangerous -->
+ <p>Protection level: dangerous
+ @hide
+ -->
<permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE"
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_bodySensorsWristTemperature"
@@ -1756,7 +1758,7 @@
android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
android:protectionLevel="dangerous" />
- <!-- Allows an application to access wrist temperature data from the watch sensors.
+ <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
If you're requesting this permission, you must also request
{@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
give you wrist temperature body sensors access.
@@ -1766,6 +1768,7 @@
<p> This is a hard restricted permission which cannot be held by an app until
the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ @hide
-->
<permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
android:permissionGroup="android.permission-group.UNDEFINED"
@@ -4205,14 +4208,14 @@
<permission android:name="android.permission.WRITE_DEVICE_CONFIG"
android:protectionLevel="signature|verifier|configurator"/>
- <!-- @SystemApi @TestApi @hide Allows an application to read/write sync disabled mode config.
+ <!-- @SystemApi @TestApi @hide Allows an application to modify only allowlisted settings.
<p>Not for use by third-party applications. -->
- <permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"
+ <permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"
android:protectionLevel="signature|verifier|configurator"/>
- <!-- @SystemApi @TestApi @hide Allows an application to modify only allowlisted settings.
+ <!-- @SystemApi @TestApi @hide Allows an application to read/write sync disabled mode config.
<p>Not for use by third-party applications. -->
- <permission android:name="android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG"
+ <permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"
android:protectionLevel="signature|verifier|configurator"/>
<!-- @SystemApi @hide Allows an application to read config settings.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 239747a37ec6..1a85f4cad9e0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5223,6 +5223,11 @@
of known compatibility issues. -->
<string-array name="config_highRefreshRateBlacklist"></string-array>
+ <!-- The list of packages to force slowJpegMode for Apps using Camera API1 -->
+ <string-array name="config_forceSlowJpegModeList" translatable="false">
+ <!-- Add packages here -->
+ </string-array>
+
<!-- Whether or not to hide the navigation bar when the soft keyboard is visible in order to
create additional screen real estate outside beyond the keyboard. Note that the user needs
to have a confirmed way to dismiss the keyboard when desired. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a09cee797878..79f3dcd8c1ed 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4196,6 +4196,7 @@
<java-symbol type="string" name="config_factoryResetPackage" />
<java-symbol type="array" name="config_highRefreshRateBlacklist" />
+ <java-symbol type="array" name="config_forceSlowJpegModeList" />
<java-symbol type="layout" name="chooser_dialog" />
<java-symbol type="layout" name="chooser_dialog_item" />
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 22462eb11a8f..0b29973507d2 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -2070,25 +2070,29 @@ public final class ImageDecoder implements AutoCloseable {
private static boolean sIsP010SupportedForAV1 = false;
private static boolean sIsP010SupportedForAV1Initialized = false;
+ private static final Object sIsP010SupportedForAV1Lock = new Object();
/**
* Checks if the device supports decoding 10-bit AV1.
*/
+ @SuppressWarnings("AndroidFrameworkCompatChange") // This is not an app-visible API.
private static boolean isP010SupportedForAV1() {
- if (sIsP010SupportedForAV1Initialized) {
- return sIsP010SupportedForAV1;
- }
+ synchronized (sIsP010SupportedForAV1Lock) {
+ if (sIsP010SupportedForAV1Initialized) {
+ return sIsP010SupportedForAV1;
+ }
- sIsP010SupportedForAV1Initialized = true;
+ sIsP010SupportedForAV1Initialized = true;
- if (hasHardwareDecoder("video/av01")) {
- sIsP010SupportedForAV1 = true;
- return true;
- }
+ if (hasHardwareDecoder("video/av01")) {
+ sIsP010SupportedForAV1 = true;
+ return true;
+ }
- sIsP010SupportedForAV1 = SystemProperties.getInt("ro.product.first_api_level", 0) >=
- Build.VERSION_CODES.S;
- return sIsP010SupportedForAV1;
+ sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT
+ >= Build.VERSION_CODES.S;
+ return sIsP010SupportedForAV1;
+ }
}
/**
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
index 5af40200d240..bd48ad2cef44 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
@@ -19,10 +19,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
+ android:id="@+id/background_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_background"
- android:elevation="@dimen/pip_menu_elevation"/>
+ android:elevation="@dimen/pip_menu_elevation_no_menu"/>
</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 0b61d7a85d9e..adbf65648dd1 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -36,7 +36,9 @@
<dimen name="pip_menu_arrow_size">24dp</dimen>
<dimen name="pip_menu_arrow_elevation">5dp</dimen>
- <dimen name="pip_menu_elevation">1dp</dimen>
+ <dimen name="pip_menu_elevation_no_menu">1dp</dimen>
+ <dimen name="pip_menu_elevation_move_menu">7dp</dimen>
+ <dimen name="pip_menu_elevation_all_actions_menu">4dp</dimen>
<dimen name="pip_menu_edu_text_view_height">24dp</dimen>
<dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 4805ed39e1a2..5f2b63089009 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -656,6 +656,13 @@ public class Bubble implements BubbleViewProvider {
}
/**
+ * Whether this bubble is conversation
+ */
+ public boolean isConversation() {
+ return null != mShortcutInfo;
+ }
+
+ /**
* Sets whether this notification should be suppressed in the shade.
*/
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 4b4b1af3662d..3dbb745f0c6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -88,7 +88,6 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
@@ -110,6 +109,8 @@ import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -1706,7 +1707,7 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Whether an intent is properly configured to display in a
- * {@link com.android.wm.shell.TaskView}.
+ * {@link TaskView}.
*
* Keep checks in sync with BubbleExtractor#canLaunchInTaskView. Typically
* that should filter out any invalid bubbles, but should protect SysUI side just in case.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 9ccd6ebc51e2..a317c449621b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -67,10 +67,10 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTaskController;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index deb4fd5f19bb..66241628fc77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -33,7 +33,6 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -575,7 +574,7 @@ public class BubbleStackView extends FrameLayout
if (maybeShowStackEdu()) {
mShowedUserEducationInTouchListenerActive = true;
return true;
- } else if (isStackEduShowing()) {
+ } else if (isStackEduVisible()) {
mStackEduView.hide(false /* fromExpansion */);
}
@@ -651,7 +650,7 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
mStackEduView.hide(false /* fromExpansion */);
}
mStackAnimationController.moveStackFromTouch(
@@ -733,8 +732,7 @@ public class BubbleStackView extends FrameLayout
@Override
public void onMove(float dx, float dy) {
- if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE)
- || isStackEduShowing()) {
+ if (isManageEduVisible() || isStackEduVisible()) {
return;
}
@@ -996,7 +994,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.updateResources();
mBubbleOverflow.updateResources();
- if (!isStackEduShowing() && mRelativeStackPositionBeforeRotation != null) {
+ if (!isStackEduVisible() && mRelativeStackPositionBeforeRotation != null) {
mStackAnimationController.setStackPosition(
mRelativeStackPositionBeforeRotation);
mRelativeStackPositionBeforeRotation = null;
@@ -1046,9 +1044,9 @@ public class BubbleStackView extends FrameLayout
setOnClickListener(view -> {
if (mShowingManage) {
showManageMenu(false /* show */);
- } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ } else if (isManageEduVisible()) {
mManageEduView.hide();
- } else if (isStackEduShowing()) {
+ } else if (isStackEduVisible()) {
mStackEduView.hide(false /* isExpanding */);
} else if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
@@ -1247,10 +1245,19 @@ public class BubbleStackView extends FrameLayout
}
/**
+ * Whether the selected bubble is conversation bubble
+ */
+ private boolean isConversationBubble() {
+ BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
+ return bubble instanceof Bubble && ((Bubble) bubble).isConversation();
+ }
+
+ /**
* Whether the educational view should show for the expanded view "manage" menu.
*/
private boolean shouldShowManageEdu() {
- if (ActivityManager.isRunningInTestHarness()) {
+ if (!isConversationBubble()) {
+ // We only show user education for conversation bubbles right now
return false;
}
final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
@@ -1273,11 +1280,17 @@ public class BubbleStackView extends FrameLayout
mManageEduView.show(mExpandedBubble.getExpandedView());
}
+ @VisibleForTesting
+ public boolean isManageEduVisible() {
+ return mManageEduView != null && mManageEduView.getVisibility() == VISIBLE;
+ }
+
/**
* Whether education view should show for the collapsed stack.
*/
private boolean shouldShowStackEdu() {
- if (ActivityManager.isRunningInTestHarness()) {
+ if (!isConversationBubble()) {
+ // We only show user education for conversation bubbles right now
return false;
}
final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
@@ -1310,13 +1323,14 @@ public class BubbleStackView extends FrameLayout
return mStackEduView.show(mPositioner.getDefaultStartPosition());
}
- private boolean isStackEduShowing() {
+ @VisibleForTesting
+ public boolean isStackEduVisible() {
return mStackEduView != null && mStackEduView.getVisibility() == VISIBLE;
}
// Recreates & shows the education views. Call when a theme/config change happens.
private void updateUserEdu() {
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
removeView(mStackEduView);
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
@@ -1325,7 +1339,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
mStackEduView.show(mPositioner.getDefaultStartPosition());
}
- if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ if (isManageEduVisible()) {
removeView(mManageEduView);
mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
@@ -1429,7 +1443,7 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.updateResources();
mDismissView.updateResources();
mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
- if (!isStackEduShowing()) {
+ if (!isStackEduVisible()) {
mStackAnimationController.setStackPosition(
new RelativeStackPosition(
mPositioner.getRestingPosition(),
@@ -2013,7 +2027,7 @@ public class BubbleStackView extends FrameLayout
if (mIsExpanded) {
if (mShowingManage) {
showManageMenu(false);
- } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ } else if (isManageEduVisible()) {
mManageEduView.hide();
} else {
mBubbleData.setExpanded(false);
@@ -2158,7 +2172,7 @@ public class BubbleStackView extends FrameLayout
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
mStackEduView.hide(true /* fromExpansion */);
}
beforeExpandedViewAnimation();
@@ -2280,7 +2294,7 @@ public class BubbleStackView extends FrameLayout
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
- if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2677,7 +2691,7 @@ public class BubbleStackView extends FrameLayout
if (flyoutMessage == null
|| flyoutMessage.message == null
|| !bubble.showFlyout()
- || isStackEduShowing()
+ || isStackEduVisible()
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
@@ -2800,7 +2814,7 @@ public class BubbleStackView extends FrameLayout
* them.
*/
public void getTouchableRegion(Rect outRect) {
- if (isStackEduShowing()) {
+ if (isStackEduVisible()) {
// When user education shows then capture all touches
outRect.set(0, 0, getWidth(), getHeight());
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 2a3162931648..7a5815994dd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -34,10 +34,10 @@ import android.view.View;
import androidx.annotation.Nullable;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTaskController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
/**
* Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 57b5b8f24fad..9808c591592f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -32,9 +32,6 @@ import com.android.wm.shell.R;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewFactory;
-import com.android.wm.shell.TaskViewFactoryController;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
@@ -93,6 +90,9 @@ import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellInterface;
+import com.android.wm.shell.taskview.TaskViewFactory;
+import com.android.wm.shell.taskview.TaskViewFactoryController;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f8743ed23aaa..e2cd7a0d1d77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -30,7 +30,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -89,6 +88,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java
new file mode 100644
index 000000000000..0221db836dda
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * This view is part of the Tv PiP menu. It is drawn behind the PiP surface and serves as a
+ * background behind the PiP content. If the PiP content is translucent, this view is visible
+ * behind it.
+ * It is also used to draw the shadow behind the Tv PiP menu. The shadow intensity is determined
+ * by the menu mode that the Tv PiP menu is in. See {@link TvPipMenuController.TvPipMenuMode}.
+ */
+class TvPipBackgroundView extends FrameLayout {
+ private static final String TAG = "TvPipBackgroundView";
+
+ private final View mBackgroundView;
+ private final int mElevationNoMenu;
+ private final int mElevationMoveMenu;
+ private final int mElevationAllActionsMenu;
+ private final int mPipMenuFadeAnimationDuration;
+
+ private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
+
+ TvPipBackgroundView(@NonNull Context context) {
+ super(context, null, 0, 0);
+ inflate(context, R.layout.tv_pip_menu_background, this);
+
+ mBackgroundView = findViewById(R.id.background_view);
+
+ final Resources res = mContext.getResources();
+ mElevationNoMenu = res.getDimensionPixelSize(R.dimen.pip_menu_elevation_no_menu);
+ mElevationMoveMenu = res.getDimensionPixelSize(R.dimen.pip_menu_elevation_move_menu);
+ mElevationAllActionsMenu =
+ res.getDimensionPixelSize(R.dimen.pip_menu_elevation_all_actions_menu);
+ mPipMenuFadeAnimationDuration =
+ res.getInteger(R.integer.tv_window_menu_fade_animation_duration);
+ }
+
+ void transitionToMenuMode(@TvPipMenuController.TvPipMenuMode int pipMenuMode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: transitionToMenuMode(), old menu mode = %s, new menu mode = %s",
+ TAG, TvPipMenuController.getMenuModeString(mCurrentMenuMode),
+ TvPipMenuController.getMenuModeString(pipMenuMode));
+
+ if (mCurrentMenuMode == pipMenuMode) return;
+
+ int elevation = mElevationNoMenu;
+ Interpolator interpolator = TvPipInterpolators.ENTER;
+ switch(pipMenuMode) {
+ case MODE_NO_MENU:
+ elevation = mElevationNoMenu;
+ interpolator = TvPipInterpolators.EXIT;
+ break;
+ case MODE_MOVE_MENU:
+ elevation = mElevationMoveMenu;
+ break;
+ case MODE_ALL_ACTIONS_MENU:
+ elevation = mElevationAllActionsMenu;
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ interpolator = TvPipInterpolators.EXIT;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TV PiP menu mode: " + pipMenuMode);
+ }
+
+ mBackgroundView.animate()
+ .translationZ(elevation)
+ .setInterpolator(interpolator)
+ .setDuration(mPipMenuFadeAnimationDuration)
+ .start();
+
+ mCurrentMenuMode = pipMenuMode;
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 2c6ca1af62a6..be1f800b9d2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -27,7 +27,6 @@ import android.content.IntentFilter;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Handler;
-import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
@@ -61,7 +60,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
- private View mPipBackgroundView;
+ private TvPipBackgroundView mPipBackgroundView;
private boolean mMenuIsFocused;
@TvPipMenuMode
@@ -178,12 +177,16 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void attachPipBackgroundView() {
- mPipBackgroundView = LayoutInflater.from(mContext)
- .inflate(R.layout.tv_pip_menu_background, null);
+ mPipBackgroundView = createTvPipBackgroundView();
setUpViewSurfaceZOrder(mPipBackgroundView, -1);
addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
}
+ @VisibleForTesting
+ TvPipBackgroundView createTvPipBackgroundView() {
+ return new TvPipBackgroundView(mContext);
+ }
+
private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -415,10 +418,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
- if (mPipMenuView == null) return;
+ if (mPipMenuView == null || mPipBackgroundView == null) return;
mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
+ mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 7a6aec718006..e4d8c32eb5c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewBase.java
index 3d0a8fd83819..5fdb60d2d342 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewBase.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.app.ActivityManager;
import android.graphics.Rect;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
index a29e7a085a21..a7e4b0119480 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.annotation.UiContext;
import android.content.Context;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index 735d9bce2059..7eed5883043d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import android.annotation.UiContext;
import android.content.Context;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -51,6 +52,9 @@ public class TaskViewFactoryController {
mTaskViewTransitions = null;
}
+ /**
+ * @return the underlying {@link TaskViewFactory}.
+ */
public TaskViewFactory asTaskViewFactory() {
return mImpl;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 080b171f4d40..646d55e4581c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -35,6 +35,7 @@ import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 306d6196c553..9b995c5dc621 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6478fe723027..c45e3fc4e0c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -26,6 +26,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Log;
import android.view.Choreographer;
@@ -38,6 +39,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.window.WindowContainerTransaction;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -81,6 +83,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final int mHandleMenuCornerRadiusId = R.dimen.caption_menu_corner_radius;
private PointF mHandleMenuPosition = new PointF();
+ private Drawable mAppIcon;
+ private CharSequence mAppName;
+
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
@@ -95,6 +100,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+
+ loadAppInfo();
}
@Override
@@ -185,7 +192,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mWindowDecorViewHolder = new DesktopModeAppControlsWindowDecorationViewHolder(
mResult.mRootView,
mOnCaptionTouchListener,
- mOnCaptionButtonClickListener
+ mOnCaptionButtonClickListener,
+ mAppName,
+ mAppIcon
);
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
@@ -243,22 +252,24 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final ImageView appIcon = menu.findViewById(R.id.application_icon);
final TextView appName = menu.findViewById(R.id.application_name);
- loadAppInfo(appName, appIcon);
+ appIcon.setImageDrawable(mAppIcon);
+ appName.setText(mAppName);
}
boolean isHandleMenuActive() {
return mHandleMenu != null;
}
- private void loadAppInfo(TextView appNameTextView, ImageView appIconImageView) {
+ private void loadAppInfo() {
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
try {
- // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
+ IconProvider provider = new IconProvider(mContext);
+ mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+ PackageManager.ComponentInfoFlags.of(0)));
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
- appNameTextView.setText(pm.getApplicationLabel(applicationInfo));
- appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo));
+ mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package not found: " + packageName, e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 95b5051cb81d..78cfcbd27ed6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -1,10 +1,9 @@
package com.android.wm.shell.windowdecor.viewholder
import android.app.ActivityManager.RunningTaskInfo
-import android.content.pm.PackageManager
import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
-import android.util.Log
import android.view.View
import android.widget.ImageButton
import android.widget.ImageView
@@ -19,7 +18,9 @@ import com.android.wm.shell.R
internal class DesktopModeAppControlsWindowDecorationViewHolder(
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
- onCaptionButtonClickListener: View.OnClickListener
+ onCaptionButtonClickListener: View.OnClickListener,
+ appName: CharSequence,
+ appIcon: Drawable
) : DesktopModeWindowDecorationViewHolder(rootView) {
private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
@@ -35,10 +36,11 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
captionHandle.setOnTouchListener(onCaptionTouchListener)
openMenuButton.setOnClickListener(onCaptionButtonClickListener)
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+ appNameTextView.text = appName
+ appIconImageView.setImageDrawable(appIcon)
}
override fun bindData(taskInfo: RunningTaskInfo) {
- bindAppInfo(taskInfo)
val captionDrawable = captionView.background as GradientDrawable
captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
@@ -50,20 +52,6 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
}
- private fun bindAppInfo(taskInfo: RunningTaskInfo) {
- val packageName: String = taskInfo.realActivity.packageName
- val pm: PackageManager = context.applicationContext.packageManager
- try {
- // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
- val applicationInfo = pm.getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0))
- appNameTextView.text = pm.getApplicationLabel(applicationInfo)
- appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo))
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Package not found: $packageName", e)
- }
- }
-
private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_app_name_light)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index e8f3f69ca64e..de967bfa288b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -29,6 +29,8 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -162,4 +164,27 @@ public class BubbleTest extends ShellTestCase {
verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any());
}
+
+ @Test
+ public void testBubbleIsConversation_hasConversationShortcut() {
+ Bubble bubble = createBubbleWithShortcut();
+ assertThat(bubble.getShortcutInfo()).isNotNull();
+ assertThat(bubble.isConversation()).isTrue();
+ }
+
+ @Test
+ public void testBubbleIsConversation_hasNoShortcut() {
+ Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
+ assertThat(bubble.getShortcutInfo()).isNull();
+ assertThat(bubble.isConversation()).isFalse();
+ }
+
+ private Bubble createBubbleWithShortcut() {
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("mockShortcutId")
+ .build();
+ return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL,
+ "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
+ mMainExecutor, mBubbleMetadataFlagListener);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
index 7c6037c96360..3a08d32bc430 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -57,6 +57,8 @@ public class TvPipMenuControllerTest extends ShellTestCase {
private TvPipActionsProvider mMockActionsProvider;
@Mock
private TvPipMenuView mMockTvPipMenuView;
+ @Mock
+ private TvPipBackgroundView mMockTvPipBackgroundView;
private TvPipMenuController mTvPipMenuController;
@@ -173,6 +175,7 @@ public class TvPipMenuControllerTest extends ShellTestCase {
assertMenuIsInAllActionsMode();
verify(mMockDelegate, times(2)).onInMoveModeChanged();
verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+ verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
}
@Test
@@ -215,6 +218,7 @@ public class TvPipMenuControllerTest extends ShellTestCase {
assertMenuIsInAllActionsMode();
verify(mMockDelegate, times(2)).onInMoveModeChanged();
verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+ verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
pressBackAndAssertMenuClosed();
}
@@ -262,12 +266,14 @@ public class TvPipMenuControllerTest extends ShellTestCase {
assertMenuIsInMoveMode();
verify(mMockDelegate).onInMoveModeChanged();
verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
+ verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_MOVE_MENU));
}
private void showAndAssertAllActionsMenu() {
mTvPipMenuController.showMenu();
assertMenuIsInAllActionsMode();
verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
+ verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
}
private void closeMenuAndAssertMenuClosed() {
@@ -284,6 +290,7 @@ public class TvPipMenuControllerTest extends ShellTestCase {
assertMenuIsOpen(false);
verify(mMockDelegate).onMenuClosed();
verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
+ verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_NO_MENU));
}
private void assertMenuIsOpen(boolean open) {
@@ -320,5 +327,10 @@ public class TvPipMenuControllerTest extends ShellTestCase {
TvPipMenuView createTvPipMenuView() {
return mMockTvPipMenuView;
}
+
+ @Override
+ TvPipBackgroundView createTvPipBackgroundView() {
+ return mMockTvPipBackgroundView;
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 62bfd17cefba..b6d7ff3cd5cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.taskview;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -53,6 +53,8 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
diff --git a/libs/dream/lowlight/Android.bp b/libs/dream/lowlight/Android.bp
index 5b5b0f07cabd..e4d2e022cd76 100644
--- a/libs/dream/lowlight/Android.bp
+++ b/libs/dream/lowlight/Android.bp
@@ -25,6 +25,7 @@ filegroup {
name: "low_light_dream_lib-sources",
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
],
path: "src",
}
@@ -37,10 +38,15 @@ android_library {
resource_dirs: [
"res",
],
+ libs: [
+ "kotlin-annotations",
+ ],
static_libs: [
"androidx.arch.core_core-runtime",
"dagger2",
"jsr330",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
diff --git a/libs/dream/lowlight/res/values/config.xml b/libs/dream/lowlight/res/values/config.xml
index 70fe0738a6f4..78fefbf41141 100644
--- a/libs/dream/lowlight/res/values/config.xml
+++ b/libs/dream/lowlight/res/values/config.xml
@@ -17,4 +17,7 @@
<resources>
<!-- The dream component used when the device is low light environment. -->
<string translatable="false" name="config_lowLightDreamComponent"/>
+ <!-- The max number of milliseconds to wait for the low light transition before setting
+ the system dream component -->
+ <integer name="config_lowLightTransitionTimeoutMs">2000</integer>
</resources>
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
deleted file mode 100644
index 3125f088c72b..000000000000
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ /dev/null
@@ -1,122 +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.dream.lowlight;
-
-import static com.android.dream.lowlight.dagger.LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT;
-
-import android.annotation.IntDef;
-import android.annotation.RequiresPermission;
-import android.app.DreamManager;
-import android.content.ComponentName;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Maintains the ambient light mode of the environment the device is in, and sets a low light dream
- * component, if present, as the system dream when the ambient light mode is low light.
- *
- * @hide
- */
-public final class LowLightDreamManager {
- private static final String TAG = "LowLightDreamManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "AMBIENT_LIGHT_MODE_" }, value = {
- AMBIENT_LIGHT_MODE_UNKNOWN,
- AMBIENT_LIGHT_MODE_REGULAR,
- AMBIENT_LIGHT_MODE_LOW_LIGHT
- })
- public @interface AmbientLightMode {}
-
- /**
- * Constant for ambient light mode being unknown.
- * @hide
- */
- public static final int AMBIENT_LIGHT_MODE_UNKNOWN = 0;
-
- /**
- * Constant for ambient light mode being regular / bright.
- * @hide
- */
- public static final int AMBIENT_LIGHT_MODE_REGULAR = 1;
-
- /**
- * Constant for ambient light mode being low light / dim.
- * @hide
- */
- public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
-
- private final DreamManager mDreamManager;
- private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
-
- @Nullable
- private final ComponentName mLowLightDreamComponent;
-
- private int mAmbientLightMode = AMBIENT_LIGHT_MODE_UNKNOWN;
-
- @Inject
- public LowLightDreamManager(
- DreamManager dreamManager,
- LowLightTransitionCoordinator lowLightTransitionCoordinator,
- @Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
- mDreamManager = dreamManager;
- mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
- mLowLightDreamComponent = lowLightDreamComponent;
- }
-
- /**
- * Sets the current ambient light mode.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
- public void setAmbientLightMode(@AmbientLightMode int ambientLightMode) {
- if (mLowLightDreamComponent == null) {
- if (DEBUG) {
- Log.d(TAG, "ignore ambient light mode change because low light dream component "
- + "is empty");
- }
- return;
- }
-
- if (mAmbientLightMode == ambientLightMode) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "ambient light mode changed from " + mAmbientLightMode + " to "
- + ambientLightMode);
- }
-
- mAmbientLightMode = ambientLightMode;
-
- boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
- mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
- () -> mDreamManager.setSystemDreamComponent(
- shouldEnterLowLight ? mLowLightDreamComponent : null));
- }
-}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
new file mode 100644
index 000000000000..96bfb78eff0d
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.Manifest
+import android.annotation.IntDef
+import android.annotation.RequiresPermission
+import android.app.DreamManager
+import android.content.ComponentName
+import android.util.Log
+import com.android.dream.lowlight.dagger.LowLightDreamModule
+import com.android.dream.lowlight.dagger.qualifiers.Application
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+/**
+ * Maintains the ambient light mode of the environment the device is in, and sets a low light dream
+ * component, if present, as the system dream when the ambient light mode is low light.
+ *
+ * @hide
+ */
+class LowLightDreamManager @Inject constructor(
+ @Application private val coroutineScope: CoroutineScope,
+ private val dreamManager: DreamManager,
+ private val lowLightTransitionCoordinator: LowLightTransitionCoordinator,
+ @param:Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
+ private val lowLightDreamComponent: ComponentName?,
+ @param:Named(LowLightDreamModule.LOW_LIGHT_TRANSITION_TIMEOUT_MS)
+ private val lowLightTransitionTimeoutMs: Long
+) {
+ /**
+ * @hide
+ */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ prefix = ["AMBIENT_LIGHT_MODE_"],
+ value = [
+ AMBIENT_LIGHT_MODE_UNKNOWN,
+ AMBIENT_LIGHT_MODE_REGULAR,
+ AMBIENT_LIGHT_MODE_LOW_LIGHT
+ ]
+ )
+ annotation class AmbientLightMode
+
+ private var mTransitionJob: Job? = null
+ private var mAmbientLightMode = AMBIENT_LIGHT_MODE_UNKNOWN
+ private val mLowLightTransitionTimeout =
+ lowLightTransitionTimeoutMs.toDuration(DurationUnit.MILLISECONDS)
+
+ /**
+ * Sets the current ambient light mode.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_DREAM_STATE)
+ fun setAmbientLightMode(@AmbientLightMode ambientLightMode: Int) {
+ if (lowLightDreamComponent == null) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "ignore ambient light mode change because low light dream component is empty"
+ )
+ }
+ return
+ }
+ if (mAmbientLightMode == ambientLightMode) {
+ return
+ }
+ if (DEBUG) {
+ Log.d(
+ TAG, "ambient light mode changed from $mAmbientLightMode to $ambientLightMode"
+ )
+ }
+ mAmbientLightMode = ambientLightMode
+ val shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
+
+ // Cancel any previous transitions
+ mTransitionJob?.cancel()
+ mTransitionJob = coroutineScope.launch {
+ try {
+ lowLightTransitionCoordinator.waitForLowLightTransitionAnimation(
+ timeout = mLowLightTransitionTimeout,
+ entering = shouldEnterLowLight
+ )
+ } catch (ex: TimeoutCancellationException) {
+ Log.e(TAG, "timed out while waiting for low light animation", ex)
+ }
+ dreamManager.setSystemDreamComponent(
+ if (shouldEnterLowLight) lowLightDreamComponent else null
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "LowLightDreamManager"
+ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+ /**
+ * Constant for ambient light mode being unknown.
+ *
+ * @hide
+ */
+ const val AMBIENT_LIGHT_MODE_UNKNOWN = 0
+
+ /**
+ * Constant for ambient light mode being regular / bright.
+ *
+ * @hide
+ */
+ const val AMBIENT_LIGHT_MODE_REGULAR = 1
+
+ /**
+ * Constant for ambient light mode being low light / dim.
+ *
+ * @hide
+ */
+ const val AMBIENT_LIGHT_MODE_LOW_LIGHT = 2
+ }
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
deleted file mode 100644
index 874a2d5af75e..000000000000
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dream.lowlight;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Helper class that allows listening and running animations before entering or exiting low light.
- */
-@Singleton
-public class LowLightTransitionCoordinator {
- /**
- * Listener that is notified before low light entry.
- */
- public interface LowLightEnterListener {
- /**
- * Callback that is notified before the device enters low light.
- *
- * @return an optional animator that will be waited upon before entering low light.
- */
- Animator onBeforeEnterLowLight();
- }
-
- /**
- * Listener that is notified before low light exit.
- */
- public interface LowLightExitListener {
- /**
- * Callback that is notified before the device exits low light.
- *
- * @return an optional animator that will be waited upon before exiting low light.
- */
- Animator onBeforeExitLowLight();
- }
-
- private LowLightEnterListener mLowLightEnterListener;
- private LowLightExitListener mLowLightExitListener;
-
- @Inject
- public LowLightTransitionCoordinator() {
- }
-
- /**
- * Sets the listener for the low light enter event.
- *
- * Only one listener can be set at a time. This method will overwrite any previously set
- * listener. Null can be used to unset the listener.
- */
- public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
- mLowLightEnterListener = lowLightEnterListener;
- }
-
- /**
- * Sets the listener for the low light exit event.
- *
- * Only one listener can be set at a time. This method will overwrite any previously set
- * listener. Null can be used to unset the listener.
- */
- public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
- mLowLightExitListener = lowLightExitListener;
- }
-
- /**
- * Notifies listeners that the device is about to enter or exit low light.
- *
- * @param entering true if listeners should be notified before entering low light, false if this
- * is notifying before exiting.
- * @param callback callback that will be run after listeners complete.
- */
- void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
- Animator animator = null;
-
- if (entering && mLowLightEnterListener != null) {
- animator = mLowLightEnterListener.onBeforeEnterLowLight();
- } else if (!entering && mLowLightExitListener != null) {
- animator = mLowLightExitListener.onBeforeExitLowLight();
- }
-
- // If the listener returned an animator to indicate it was running an animation, run the
- // callback after the animation completes, otherwise call the callback directly.
- if (animator != null) {
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- callback.run();
- }
- });
- } else {
- callback.run();
- }
- }
-}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
new file mode 100644
index 000000000000..26efb55fa560
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import com.android.dream.lowlight.util.suspendCoroutineWithTimeout
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.coroutines.resume
+import kotlin.time.Duration
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+class LowLightTransitionCoordinator @Inject constructor() {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ fun onBeforeEnterLowLight(): Animator?
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ fun onBeforeExitLowLight(): Animator?
+ }
+
+ private var mLowLightEnterListener: LowLightEnterListener? = null
+ private var mLowLightExitListener: LowLightExitListener? = null
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ fun setLowLightEnterListener(lowLightEnterListener: LowLightEnterListener?) {
+ mLowLightEnterListener = lowLightEnterListener
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ fun setLowLightExitListener(lowLightExitListener: LowLightExitListener?) {
+ mLowLightExitListener = lowLightExitListener
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light, and waits for the
+ * animation to complete. If this function is cancelled, the animation is also cancelled.
+ *
+ * @param timeout the maximum duration to wait for the transition animation. If the animation
+ * does not complete within this time period, a
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ */
+ suspend fun waitForLowLightTransitionAnimation(timeout: Duration, entering: Boolean) =
+ suspendCoroutineWithTimeout(timeout) { continuation ->
+ var animator: Animator? = null
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener!!.onBeforeEnterLowLight()
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener!!.onBeforeExitLowLight()
+ }
+
+ if (animator == null) {
+ continuation.resume(Unit)
+ return@suspendCoroutineWithTimeout
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ val listener = object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ continuation.resume(Unit)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ continuation.cancel()
+ }
+ }
+ animator.addListener(listener)
+ continuation.invokeOnCancellation {
+ animator.removeListener(listener)
+ animator.cancel()
+ }
+ }
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java
deleted file mode 100644
index c183a04cb2f9..000000000000
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.java
+++ /dev/null
@@ -1,61 +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.dream.lowlight.dagger;
-
-import android.app.DreamManager;
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.dream.lowlight.R;
-
-import javax.inject.Named;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger module for low light dream.
- *
- * @hide
- */
-@Module
-public interface LowLightDreamModule {
- String LOW_LIGHT_DREAM_COMPONENT = "low_light_dream_component";
-
- /**
- * Provides dream manager.
- */
- @Provides
- static DreamManager providesDreamManager(Context context) {
- return context.getSystemService(DreamManager.class);
- }
-
- /**
- * Provides the component name of the low light dream, or null if not configured.
- */
- @Provides
- @Named(LOW_LIGHT_DREAM_COMPONENT)
- @Nullable
- static ComponentName providesLowLightDreamComponent(Context context) {
- final String lowLightDreamComponent = context.getResources().getString(
- R.string.config_lowLightDreamComponent);
- return lowLightDreamComponent.isEmpty() ? null
- : ComponentName.unflattenFromString(lowLightDreamComponent);
- }
-}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt
new file mode 100644
index 000000000000..dd274bd9d509
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/LowLightDreamModule.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight.dagger
+
+import android.app.DreamManager
+import android.content.ComponentName
+import android.content.Context
+import com.android.dream.lowlight.R
+import com.android.dream.lowlight.dagger.qualifiers.Application
+import com.android.dream.lowlight.dagger.qualifiers.Main
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Named
+
+/**
+ * Dagger module for low light dream.
+ *
+ * @hide
+ */
+@Module
+object LowLightDreamModule {
+ /**
+ * Provides dream manager.
+ */
+ @Provides
+ fun providesDreamManager(context: Context): DreamManager {
+ return requireNotNull(context.getSystemService(DreamManager::class.java))
+ }
+
+ /**
+ * Provides the component name of the low light dream, or null if not configured.
+ */
+ @Provides
+ @Named(LOW_LIGHT_DREAM_COMPONENT)
+ fun providesLowLightDreamComponent(context: Context): ComponentName? {
+ val lowLightDreamComponent = context.resources.getString(
+ R.string.config_lowLightDreamComponent
+ )
+ return if (lowLightDreamComponent.isEmpty()) {
+ null
+ } else {
+ ComponentName.unflattenFromString(lowLightDreamComponent)
+ }
+ }
+
+ @Provides
+ @Named(LOW_LIGHT_TRANSITION_TIMEOUT_MS)
+ fun providesLowLightTransitionTimeout(context: Context): Long {
+ return context.resources.getInteger(R.integer.config_lowLightTransitionTimeoutMs).toLong()
+ }
+
+ @Provides
+ @Main
+ fun providesMainDispatcher(): CoroutineDispatcher {
+ return Dispatchers.Main.immediate
+ }
+
+ @Provides
+ @Application
+ fun providesApplicationScope(@Main dispatcher: CoroutineDispatcher): CoroutineScope {
+ return CoroutineScope(dispatcher)
+ }
+
+ const val LOW_LIGHT_DREAM_COMPONENT = "low_light_dream_component"
+ const val LOW_LIGHT_TRANSITION_TIMEOUT_MS = "low_light_transition_timeout"
+}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt
new file mode 100644
index 000000000000..541fe4017e6e
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Application.kt
@@ -0,0 +1,9 @@
+package com.android.dream.lowlight.dagger.qualifiers
+
+import android.content.Context
+import javax.inject.Qualifier
+
+/**
+ * Used to qualify a context as [Context.getApplicationContext]
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Application
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt
new file mode 100644
index 000000000000..ccd0710bdc60
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/dagger/qualifiers/Main.kt
@@ -0,0 +1,8 @@
+package com.android.dream.lowlight.dagger.qualifiers
+
+import javax.inject.Qualifier
+
+/**
+ * Used to qualify code running on the main thread.
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Main
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt
new file mode 100644
index 000000000000..ff675ccfaffb
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/KotlinUtils.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight.util
+
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeout
+import kotlin.time.Duration
+
+suspend inline fun <T> suspendCoroutineWithTimeout(
+ timeout: Duration,
+ crossinline block: (CancellableContinuation<T>) -> Unit
+) = withTimeout(timeout) {
+ suspendCancellableCoroutine(block = block)
+}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index bd6f05eabac5..2d79090cd7d4 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -20,6 +20,7 @@ android_test {
name: "LowLightDreamTests",
srcs: [
"**/*.java",
+ "**/*.kt",
],
static_libs: [
"LowLightDreamLib",
@@ -28,6 +29,7 @@ android_test {
"androidx.test.ext.junit",
"frameworks-base-testutils",
"junit",
+ "kotlinx_coroutines_test",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"testables",
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
deleted file mode 100644
index 4b95d8c84bac..000000000000
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ /dev/null
@@ -1,109 +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.dream.lowlight;
-
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT;
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR;
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.DreamManager;
-import android.content.ComponentName;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class LowLightDreamManagerTest {
- @Mock
- private DreamManager mDreamManager;
-
- @Mock
- private LowLightTransitionCoordinator mTransitionCoordinator;
-
- @Mock
- private ComponentName mDreamComponent;
-
- LowLightDreamManager mLowLightDreamManager;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing.
- doAnswer(invocation -> {
- ((Runnable) invocation.getArgument(1)).run();
- return null;
- }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(),
- any(Runnable.class));
-
- mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator,
- mDreamComponent);
- }
-
- @Test
- public void setAmbientLightMode_lowLight_setSystemDream() {
- mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
- verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
- verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
- }
-
- @Test
- public void setAmbientLightMode_regularLight_clearSystemDream() {
- mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
-
- verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
- verify(mDreamManager).setSystemDreamComponent(null);
- }
-
- @Test
- public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- // Set to low light first.
- mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
- clearInvocations(mDreamManager);
-
- // Return to default unknown mode.
- mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
-
- verify(mDreamManager).setSystemDreamComponent(null);
- }
-
- @Test
- public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mTransitionCoordinator, null /*dream component*/);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
-
- verify(mDreamManager, never()).setSystemDreamComponent(any());
- }
-}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
new file mode 100644
index 000000000000..2a886bc31788
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.animation.Animator
+import android.app.DreamManager
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import src.com.android.dream.lowlight.utils.any
+import src.com.android.dream.lowlight.utils.withArgCaptor
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LowLightDreamManagerTest {
+ @Mock
+ private lateinit var mDreamManager: DreamManager
+ @Mock
+ private lateinit var mEnterAnimator: Animator
+ @Mock
+ private lateinit var mExitAnimator: Animator
+
+ private lateinit var mTransitionCoordinator: LowLightTransitionCoordinator
+ private lateinit var mLowLightDreamManager: LowLightDreamManager
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope(StandardTestDispatcher())
+
+ mTransitionCoordinator = LowLightTransitionCoordinator()
+ mTransitionCoordinator.setLowLightEnterListener(
+ object : LowLightTransitionCoordinator.LowLightEnterListener {
+ override fun onBeforeEnterLowLight() = mEnterAnimator
+ })
+ mTransitionCoordinator.setLowLightExitListener(
+ object : LowLightTransitionCoordinator.LowLightExitListener {
+ override fun onBeforeExitLowLight() = mExitAnimator
+ })
+
+ mLowLightDreamManager = LowLightDreamManager(
+ coroutineScope = testScope,
+ dreamManager = mDreamManager,
+ lowLightTransitionCoordinator = mTransitionCoordinator,
+ lowLightDreamComponent = DREAM_COMPONENT,
+ lowLightTransitionTimeoutMs = LOW_LIGHT_TIMEOUT_MS
+ )
+ }
+
+ @Test
+ fun setAmbientLightMode_lowLight_setSystemDream() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ verify(mDreamManager, never()).setSystemDreamComponent(DREAM_COMPONENT)
+ completeEnterAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ }
+
+ @Test
+ fun setAmbientLightMode_regularLight_clearSystemDream() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
+ runCurrent()
+ verify(mDreamManager, never()).setSystemDreamComponent(null)
+ completeExitAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(null)
+ }
+
+ @Test
+ fun setAmbientLightMode_defaultUnknownMode_clearSystemDream() = testScope.runTest {
+ // Set to low light first.
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ completeEnterAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ clearInvocations(mDreamManager)
+
+ // Return to default unknown mode.
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN)
+ runCurrent()
+ completeExitAnimations()
+ runCurrent()
+ verify(mDreamManager).setSystemDreamComponent(null)
+ }
+
+ @Test
+ fun setAmbientLightMode_dreamComponentNotSet_doNothing() = testScope.runTest {
+ val lowLightDreamManager = LowLightDreamManager(
+ coroutineScope = testScope,
+ dreamManager = mDreamManager,
+ lowLightTransitionCoordinator = mTransitionCoordinator,
+ lowLightDreamComponent = null,
+ lowLightTransitionTimeoutMs = LOW_LIGHT_TIMEOUT_MS
+ )
+ lowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ verify(mEnterAnimator, never()).addListener(any())
+ verify(mDreamManager, never()).setSystemDreamComponent(any())
+ }
+
+ @Test
+ fun setAmbientLightMode_multipleTimesBeforeAnimationEnds_cancelsPrevious() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ // If we reset the light mode back to regular before the previous animation finishes, it
+ // should be ignored.
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
+ runCurrent()
+ completeEnterAnimations()
+ completeExitAnimations()
+ runCurrent()
+ verify(mDreamManager, times(1)).setSystemDreamComponent(null)
+ }
+
+ @Test
+ fun setAmbientLightMode_animatorNeverFinishes_timesOut() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ advanceTimeBy(delayTimeMillis = LOW_LIGHT_TIMEOUT_MS + 1)
+ // Animation never finishes, but we should still set the system dream
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ }
+
+ private fun completeEnterAnimations() {
+ val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
+ listener.onAnimationEnd(mEnterAnimator)
+ }
+
+ private fun completeExitAnimations() {
+ val listener = withArgCaptor { verify(mExitAnimator).addListener(capture()) }
+ listener.onAnimationEnd(mExitAnimator)
+ }
+
+ companion object {
+ private val DREAM_COMPONENT = ComponentName("test_package", "test_dream")
+ private const val LOW_LIGHT_TIMEOUT_MS: Long = 1000
+ }
+} \ No newline at end of file
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
deleted file mode 100644
index 81e1e33d6220..000000000000
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dream.lowlight;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.animation.Animator;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class LowLightTransitionCoordinatorTest {
- @Mock
- private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
-
- @Mock
- private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
-
- @Mock
- private Animator mAnimator;
-
- @Captor
- private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor;
-
- @Mock
- private Runnable mRunnable;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void onEnterCalledOnListeners() {
- LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
-
- coordinator.setLowLightEnterListener(mEnterListener);
-
- coordinator.notifyBeforeLowLightTransition(true, mRunnable);
-
- verify(mEnterListener).onBeforeEnterLowLight();
- verify(mRunnable).run();
- }
-
- @Test
- public void onExitCalledOnListeners() {
- LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
-
- coordinator.setLowLightExitListener(mExitListener);
-
- coordinator.notifyBeforeLowLightTransition(false, mRunnable);
-
- verify(mExitListener).onBeforeExitLowLight();
- verify(mRunnable).run();
- }
-
- @Test
- public void listenerNotCalledAfterRemoval() {
- LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
-
- coordinator.setLowLightEnterListener(mEnterListener);
- coordinator.setLowLightEnterListener(null);
-
- coordinator.notifyBeforeLowLightTransition(true, mRunnable);
-
- verifyZeroInteractions(mEnterListener);
- verify(mRunnable).run();
- }
-
- @Test
- public void runnableCalledAfterAnimationEnds() {
- when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
-
- LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
- coordinator.setLowLightEnterListener(mEnterListener);
-
- coordinator.notifyBeforeLowLightTransition(true, mRunnable);
-
- // Animator listener is added and the runnable is not run yet.
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
- verifyZeroInteractions(mRunnable);
-
- // Runnable is run once the animation ends.
- mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
- verify(mRunnable).run();
- }
-}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
new file mode 100644
index 000000000000..4c526a6ac69d
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight
+
+import android.animation.Animator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dream.lowlight.LowLightTransitionCoordinator.LowLightEnterListener
+import com.android.dream.lowlight.LowLightTransitionCoordinator.LowLightExitListener
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import src.com.android.dream.lowlight.utils.whenever
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LowLightTransitionCoordinatorTest {
+ @Mock
+ private lateinit var mEnterListener: LowLightEnterListener
+
+ @Mock
+ private lateinit var mExitListener: LowLightExitListener
+
+ @Mock
+ private lateinit var mAnimator: Animator
+
+ @Captor
+ private lateinit var mAnimatorListenerCaptor: ArgumentCaptor<Animator.AnimatorListener>
+
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope(StandardTestDispatcher())
+ }
+
+ @Test
+ fun onEnterCalledOnListeners() = testScope.runTest {
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ verify(mEnterListener).onBeforeEnterLowLight()
+ assertThat(job.isCompleted).isTrue()
+ }
+
+ @Test
+ fun onExitCalledOnListeners() = testScope.runTest {
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightExitListener(mExitListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = false)
+ }
+ runCurrent()
+ verify(mExitListener).onBeforeExitLowLight()
+ assertThat(job.isCompleted).isTrue()
+ }
+
+ @Test
+ fun listenerNotCalledAfterRemoval() = testScope.runTest {
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ coordinator.setLowLightEnterListener(null)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ verify(mEnterListener, never()).onBeforeEnterLowLight()
+ assertThat(job.isCompleted).isTrue()
+ }
+
+ @Test
+ fun waitsForAnimationToEnd() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
+ assertThat(job.isCompleted).isFalse()
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.value.onAnimationEnd(mAnimator)
+ runCurrent()
+ assertThat(job.isCompleted).isTrue()
+ assertThat(job.isCancelled).isFalse()
+ }
+
+ @Test
+ fun waitsForTimeoutIfAnimationNeverEnds() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ assertThat(job.isCancelled).isFalse()
+ advanceTimeBy(delayTimeMillis = TIMEOUT.inWholeMilliseconds + 1)
+ // If animator doesn't complete within the timeout, we should cancel ourselves.
+ assertThat(job.isCancelled).isTrue()
+ }
+
+ @Test
+ fun shouldCancelIfAnimationIsCancelled() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
+ assertThat(job.isCompleted).isFalse()
+ assertThat(job.isCancelled).isFalse()
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.value.onAnimationCancel(mAnimator)
+ runCurrent()
+ assertThat(job.isCompleted).isTrue()
+ assertThat(job.isCancelled).isTrue()
+ }
+
+ @Test
+ fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest {
+ whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
+ val coordinator = LowLightTransitionCoordinator()
+ coordinator.setLowLightEnterListener(mEnterListener)
+ val job = launch {
+ coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
+ }
+ runCurrent()
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
+ verify(mAnimator, never()).cancel()
+ assertThat(job.isCompleted).isFalse()
+
+ job.cancel()
+ // We should have removed the listener and cancelled the animator
+ verify(mAnimator).removeListener(mAnimatorListenerCaptor.value)
+ verify(mAnimator).cancel()
+ }
+
+ companion object {
+ private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS)
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt
new file mode 100644
index 000000000000..e5ec26ca4b41
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/utils/KotlinMockitoHelpers.kt
@@ -0,0 +1,125 @@
+package src.com.android.dream.lowlight.utils
+
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.stubbing.OngoingStubbing
+import org.mockito.stubbing.Stubber
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+inline fun <reified T> any(): T = any(T::class.java)
+
+/**
+ * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher)
+
+/**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
+ * when null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+ ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ *
+ * @param apply builder function to simplify stub configuration by improving type inference.
+ */
+inline fun <reified T : Any> mock(apply: T.() -> Unit = {}): T = Mockito.mock(T::class.java)
+ .apply(apply)
+
+/**
+ * Helper function for stubbing methods without the need to use backticks.
+ *
+ * @see Mockito.when
+ */
+fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+fun <T> Stubber.whenever(mock: T): T = `when`(mock)
+
+/**
+ * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
+ * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
+ *
+ * java.lang.NullPointerException: capture() must not be null
+ */
+class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
+ private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
+ fun capture(): T = wrapped.capture()
+ val value: T
+ get() = wrapped.value
+ val allValues: List<T>
+ get() = wrapped.allValues
+}
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
+ KotlinArgumentCaptor(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured = captor.value
+ *
+ * becomes:
+ *
+ * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ *
+ * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
+ */
+inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
+ kotlinArgumentCaptor<T>().apply { block() }.value
+
+/**
+ * Variant of [withArgCaptor] for capturing multiple arguments.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured: List<Foo> = captor.allValues
+ *
+ * becomes:
+ *
+ * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) }
+ */
+inline fun <reified T : Any> captureMany(block: KotlinArgumentCaptor<T>.() -> Unit): List<T> =
+ kotlinArgumentCaptor<T>().apply{ block() }.allValues
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 3ba1d1f0eca2..c1ee74a70a15 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -75,11 +75,13 @@ public class AudioPolicy {
*/
public static final int POLICY_STATUS_REGISTERED = 2;
+ @GuardedBy("mLock")
private int mStatus;
+ @GuardedBy("mLock")
private String mRegistrationId;
- private AudioPolicyStatusListener mStatusListener;
- private boolean mIsFocusPolicy;
- private boolean mIsTestFocusPolicy;
+ private final AudioPolicyStatusListener mStatusListener;
+ private final boolean mIsFocusPolicy;
+ private final boolean mIsTestFocusPolicy;
/**
* The list of AudioTrack instances created to inject audio into the associated mixes
@@ -115,6 +117,7 @@ public class AudioPolicy {
private Context mContext;
+ @GuardedBy("mLock")
private AudioPolicyConfig mConfig;
private final MediaProjection mProjection;
@@ -552,7 +555,6 @@ public class AudioPolicy {
/** @hide */
public void reset() {
setRegistration(null);
- mConfig.reset();
}
public void setRegistration(String regId) {
@@ -563,6 +565,7 @@ public class AudioPolicy {
mStatus = POLICY_STATUS_REGISTERED;
} else {
mStatus = POLICY_STATUS_UNREGISTERED;
+ mConfig.reset();
}
}
sendMsg(MSG_POLICY_STATUS_CHANGE);
@@ -940,14 +943,9 @@ public class AudioPolicy {
}
private void onPolicyStatusChange() {
- AudioPolicyStatusListener l;
- synchronized (mLock) {
- if (mStatusListener == null) {
- return;
- }
- l = mStatusListener;
+ if (mStatusListener != null) {
+ mStatusListener.onStatusChange();
}
- l.onStatusChange();
}
//==================================================
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index ce9773312a10..7a85d21bf144 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -42,9 +42,7 @@ public class AudioPolicyConfig implements Parcelable {
private String mRegistrationId = null;
- /** counter for the mixes that are / have been in the list of AudioMix
- * e.g. register 4 mixes (counter is 3), remove 1 (counter is 3), add 1 (counter is 4)
- */
+ // Corresponds to id of next mix to be registered.
private int mMixCounter = 0;
protected AudioPolicyConfig(AudioPolicyConfig conf) {
@@ -286,7 +284,7 @@ public class AudioPolicyConfig implements Parcelable {
if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
AudioMix.ROUTE_FLAG_LOOP_BACK) {
mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
- + mMixCounter);
+ + mMixCounter++);
} else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
AudioMix.ROUTE_FLAG_RENDER) {
mix.setRegistration(mix.mDeviceAddress);
@@ -294,7 +292,6 @@ public class AudioPolicyConfig implements Parcelable {
} else {
mix.setRegistration("");
}
- mMixCounter++;
}
@GuardedBy("mMixes")
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index 2bdd5c8bc977..5f7d636fdd1e 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -23,22 +23,32 @@ import android.os.IBinder;
interface IMediaProjection {
void start(IMediaProjectionCallback callback);
void stop();
+
boolean canProjectAudio();
boolean canProjectVideo();
boolean canProjectSecureVideo();
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
int applyVirtualDisplayFlags(int flags);
+
void registerCallback(IMediaProjectionCallback callback);
+
void unregisterCallback(IMediaProjectionCallback callback);
/**
* Returns the {@link android.os.IBinder} identifying the task to record, or {@code null} if
* there is none.
*/
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
IBinder getLaunchCookie();
/**
* Updates the {@link android.os.IBinder} identifying the task to record, or {@code null} if
* there is none.
*/
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void setLaunchCookie(in IBinder launchCookie);
}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index c259f9ad9cf9..c97265d4939d 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -28,11 +28,20 @@ interface IMediaProjectionManager {
@UnsupportedAppUsage
boolean hasProjectionPermission(int uid, String packageName);
+ /**
+ * Returns a new {@link IMediaProjection} instance associated with the given package.
+ */
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
IMediaProjection createProjection(int uid, String packageName, int type,
boolean permanentGrant);
+ /**
+ * Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
+ * projection, or {@code false} otherwise.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
boolean isCurrentProjection(IMediaProjection projection);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
@@ -67,6 +76,8 @@ interface IMediaProjectionManager {
* @param incomingSession the nullable incoming content recording session
* @param projection the non-null projection the session describes
*/
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void setContentRecordingSession(in ContentRecordingSession incomingSession,
in IMediaProjection projection);
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 2b7bcbee79fd..cc7a7d5bb9dc 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -161,7 +161,8 @@ public class CameraBinderTest extends AndroidTestCase {
ICameraService.USE_CALLING_UID,
ICameraService.USE_CALLING_PID,
getContext().getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false);
+ /*overrideToPortrait*/false,
+ /*forceSlowJpegMode*/false);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
diff --git a/native/android/input.cpp b/native/android/input.cpp
index f1c30889c4db..432e21cb5c08 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -149,7 +149,8 @@ int32_t AMotionEvent_getPointerId(const AInputEvent* motion_event, size_t pointe
}
int32_t AMotionEvent_getToolType(const AInputEvent* motion_event, size_t pointer_index) {
- return static_cast<const MotionEvent*>(motion_event)->getToolType(pointer_index);
+ const MotionEvent& motion = static_cast<const MotionEvent&>(*motion_event);
+ return static_cast<int32_t>(motion.getToolType(pointer_index));
}
float AMotionEvent_getRawX(const AInputEvent* motion_event, size_t pointer_index) {
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1e0c2cdb6d99..0498a15269ce 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -105,6 +105,8 @@
<string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_choose_option_for">Choose an option for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <!-- This appears as the title of the dialog asking user to use a previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_use_info_on">Use this info on <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This is a label for a button that links the user to different sign-in methods . [CHAR LIMIT=80] -->
<string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
<!-- This is a label for a button that links the user to different sign-in methods. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index b3d3b6dc66d0..783cf3b47344 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -102,7 +102,7 @@ private fun getServiceLabelAndIcon(
).toString()
providerIcon = pkgInfo.applicationInfo.loadIcon(pm)
} catch (e: PackageManager.NameNotFoundException) {
- Log.e(Constants.LOG_TAG, "Provider info not found", e)
+ Log.e(Constants.LOG_TAG, "Provider package info not found", e)
}
} else {
try {
@@ -113,7 +113,23 @@ private fun getServiceLabelAndIcon(
).toString()
providerIcon = si.loadIcon(pm)
} catch (e: PackageManager.NameNotFoundException) {
- Log.e(Constants.LOG_TAG, "Provider info not found", e)
+ Log.e(Constants.LOG_TAG, "Provider service info not found", e)
+ // Added for mdoc use case where the provider may not need to register a service and
+ // instead only relies on the registration api.
+ try {
+ val pkgInfo = pm.getPackageInfo(
+ component.packageName,
+ PackageManager.PackageInfoFlags.of(0)
+ )
+ providerLabel =
+ pkgInfo.applicationInfo.loadSafeLabel(
+ pm, 0f,
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+ ).toString()
+ providerIcon = pkgInfo.applicationInfo.loadIcon(pm)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(Constants.LOG_TAG, "Provider package info not found", e)
+ }
}
}
return if (providerLabel == null || providerIcon == null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
index ba48f2b0fc1b..57fefbe577b4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
@@ -34,7 +34,9 @@ import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.common.BaseEntry
import com.android.credentialmanager.common.ProviderActivityState
+import com.android.credentialmanager.common.ui.ConfirmButton
import com.android.credentialmanager.common.ui.CredentialContainerCard
+import com.android.credentialmanager.common.ui.CtaButtonRow
import com.android.credentialmanager.common.ui.HeadlineIcon
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
@@ -62,6 +64,7 @@ fun GetGenericCredentialScreen(
providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
providerInfoList = getCredentialUiState.providerInfoList,
onEntrySelected = viewModel::getFlowOnEntrySelected,
+ onConfirm = viewModel::getFlowOnConfirmEntrySelected,
onLog = { viewModel.logUiEvent(it) },
)
viewModel.uiMetrics.log(GetCredentialEvent
@@ -93,10 +96,13 @@ fun PrimarySelectionCardGeneric(
providerDisplayInfo: ProviderDisplayInfo,
providerInfoList: List<ProviderInfo>,
onEntrySelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
onLog: @Composable (UiEventLogger.UiEventEnum) -> Unit,
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val totalEntriesCount = sortedUserNameToCredentialEntryList
+ .flatMap { it.sortedCredentialEntryList }.size
SheetContainerCard {
// When only one provider (not counting the remote-only provider) exists, display that
// provider's icon + name up top.
@@ -125,7 +131,11 @@ fun PrimarySelectionCardGeneric(
item {
HeadlineText(
text = stringResource(
- R.string.get_dialog_title_choose_option_for,
+ if (totalEntriesCount == 1) {
+ R.string.get_dialog_title_use_info_on
+ } else {
+ R.string.get_dialog_title_choose_option_for
+ },
requestDisplayInfo.appName
),
)
@@ -134,7 +144,6 @@ fun PrimarySelectionCardGeneric(
item {
CredentialContainerCard {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
- // Show max 4 entries in this primary page
sortedUserNameToCredentialEntryList.forEach {
// TODO(b/275375861): fallback UI merges entries by account names.
// Need a strategy to be able to show all entries.
@@ -147,6 +156,19 @@ fun PrimarySelectionCardGeneric(
}
}
}
+ item { Divider(thickness = 24.dp, color = Color.Transparent) }
+ item {
+ if (totalEntriesCount == 1) {
+ CtaButtonRow(
+ rightButton = {
+ ConfirmButton(
+ stringResource(R.string.get_dialog_button_label_continue),
+ onClick = onConfirm
+ )
+ }
+ )
+ }
+ }
}
onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 78b78101e64e..964e4b24d130 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -119,29 +119,15 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
}
final int restrictionSource = enforcingUsers.get(0).getUserRestrictionSource();
- final int adminUserId = enforcingUsers.get(0).getUserHandle().getIdentifier();
- if (restrictionSource == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
- // Check if it is a profile owner of the user under consideration.
- if (adminUserId == userId) {
- return getProfileOwner(context, userRestriction, adminUserId);
- } else {
- // Check if it is a profile owner of a managed profile of the current user.
- // Otherwise it is in a separate user and we return a default EnforcedAdmin.
- final UserInfo parentUser = um.getProfileParent(adminUserId);
- return (parentUser != null && parentUser.id == userId)
- ? getProfileOwner(context, userRestriction, adminUserId)
- : EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(userRestriction);
- }
- } else if (restrictionSource == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
- // When the restriction is enforced by device owner, return the device owner admin only
- // if the admin is for the {@param userId} otherwise return a default EnforcedAdmin.
- return adminUserId == userId
- ? getDeviceOwner(context, userRestriction)
- : EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(userRestriction);
+ if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
+ return null;
}
- // If the restriction is enforced by system then return null.
- return null;
+ final EnforcedAdmin admin = getProfileOrDeviceOwner(context, userHandle);
+ if (admin != null) {
+ return admin;
+ }
+ return EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(userRestriction);
}
public static boolean hasBaseUserRestriction(Context context,
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 48d449dd7daa..5e8f3a18cbc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -483,7 +483,10 @@ public class ApplicationsState {
public AppEntry getEntry(String packageName, int userId) {
if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
synchronized (mEntriesMap) {
- AppEntry entry = mEntriesMap.get(userId).get(packageName);
+ AppEntry entry = null;
+ if (mEntriesMap.contains(userId)) {
+ entry = mEntriesMap.get(userId).get(packageName);
+ }
if (entry == null) {
ApplicationInfo info = getAppInfoLocked(packageName, userId);
if (info == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 250187f210dc..df0e61833269 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -312,12 +312,6 @@ public class DataServiceUtils {
"isFirstRemovableSubscription";
/**
- * The name of the default SIM config column,
- * {@see SubscriptionUtil#getDefaultSimConfig(Context, int)}.
- */
- public static final String COLUMN_DEFAULT_SIM_CONFIG = "defaultSimConfig";
-
- /**
* The name of the default subscription selection column,
* {@see SubscriptionUtil#getSubscriptionOrDefault(Context, int)}.
*/
@@ -349,32 +343,6 @@ public class DataServiceUtils {
public static final String COLUMN_IS_AVAILABLE_SUBSCRIPTION = "isAvailableSubscription";
/**
- * The name of the default voice subscription state column, see
- * {@link SubscriptionManager#getDefaultVoiceSubscriptionId()}.
- */
- public static final String COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION =
- "isDefaultVoiceSubscription";
-
- /**
- * The name of the default sms subscription state column, see
- * {@link SubscriptionManager#getDefaultSmsSubscriptionId()}.
- */
- public static final String COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION = "isDefaultSmsSubscription";
-
- /**
- * The name of the default data subscription state column, see
- * {@link SubscriptionManager#getDefaultDataSubscriptionId()}.
- */
- public static final String COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION =
- "isDefaultDataSubscription";
-
- /**
- * The name of the default subscription state column, see
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- */
- public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
-
- /**
* The name of the active data subscription state column, see
* {@link SubscriptionManager#getActiveDataSubscriptionId()}.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 23566f760444..c40388fee710 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -37,12 +37,10 @@ public class SubscriptionInfoEntity {
String countryIso, boolean isEmbedded, int cardId, int portIndex,
boolean isOpportunistic, @Nullable String groupUUID, int subscriptionType,
String uniqueName, boolean isSubscriptionVisible, String formattedPhoneNumber,
- boolean isFirstRemovableSubscription, String defaultSimConfig,
- boolean isDefaultSubscriptionSelection, boolean isValidSubscription,
- boolean isUsableSubscription, boolean isActiveSubscriptionId,
- boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
- boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
- boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) {
+ boolean isFirstRemovableSubscription, boolean isDefaultSubscriptionSelection,
+ boolean isValidSubscription, boolean isUsableSubscription,
+ boolean isActiveSubscriptionId, boolean isAvailableSubscription,
+ boolean isActiveDataSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
this.carrierId = carrierId;
@@ -62,16 +60,11 @@ public class SubscriptionInfoEntity {
this.isSubscriptionVisible = isSubscriptionVisible;
this.formattedPhoneNumber = formattedPhoneNumber;
this.isFirstRemovableSubscription = isFirstRemovableSubscription;
- this.defaultSimConfig = defaultSimConfig;
this.isDefaultSubscriptionSelection = isDefaultSubscriptionSelection;
this.isValidSubscription = isValidSubscription;
this.isUsableSubscription = isUsableSubscription;
this.isActiveSubscriptionId = isActiveSubscriptionId;
this.isAvailableSubscription = isAvailableSubscription;
- this.isDefaultVoiceSubscription = isDefaultVoiceSubscription;
- this.isDefaultSmsSubscription = isDefaultSmsSubscription;
- this.isDefaultDataSubscription = isDefaultDataSubscription;
- this.isDefaultSubscription = isDefaultSubscription;
this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@@ -135,9 +128,6 @@ public class SubscriptionInfoEntity {
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION)
public boolean isFirstRemovableSubscription;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DEFAULT_SIM_CONFIG)
- public String defaultSimConfig;
-
@ColumnInfo(name =
DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION_SELECTION)
public boolean isDefaultSubscriptionSelection;
@@ -154,18 +144,6 @@ public class SubscriptionInfoEntity {
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_AVAILABLE_SUBSCRIPTION)
public boolean isAvailableSubscription;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION)
- public boolean isDefaultVoiceSubscription;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION)
- public boolean isDefaultSmsSubscription;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION)
- public boolean isDefaultDataSubscription;
-
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
- public boolean isDefaultSubscription;
-
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
public boolean isActiveDataSubscriptionId;
@@ -207,16 +185,11 @@ public class SubscriptionInfoEntity {
result = 31 * result + Boolean.hashCode(isSubscriptionVisible);
result = 31 * result + formattedPhoneNumber.hashCode();
result = 31 * result + Boolean.hashCode(isFirstRemovableSubscription);
- result = 31 * result + defaultSimConfig.hashCode();
result = 31 * result + Boolean.hashCode(isDefaultSubscriptionSelection);
result = 31 * result + Boolean.hashCode(isValidSubscription);
result = 31 * result + Boolean.hashCode(isUsableSubscription);
result = 31 * result + Boolean.hashCode(isActiveSubscriptionId);
result = 31 * result + Boolean.hashCode(isAvailableSubscription);
- result = 31 * result + Boolean.hashCode(isDefaultVoiceSubscription);
- result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
- result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
- result = 31 * result + Boolean.hashCode(isDefaultSubscription);
result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
return result;
}
@@ -250,16 +223,11 @@ public class SubscriptionInfoEntity {
&& isSubscriptionVisible == info.isSubscriptionVisible
&& TextUtils.equals(formattedPhoneNumber, info.formattedPhoneNumber)
&& isFirstRemovableSubscription == info.isFirstRemovableSubscription
- && TextUtils.equals(defaultSimConfig, info.defaultSimConfig)
&& isDefaultSubscriptionSelection == info.isDefaultSubscriptionSelection
&& isValidSubscription == info.isValidSubscription
&& isUsableSubscription == info.isUsableSubscription
&& isActiveSubscriptionId == info.isActiveSubscriptionId
&& isAvailableSubscription == info.isAvailableSubscription
- && isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
- && isDefaultSmsSubscription == info.isDefaultSmsSubscription
- && isDefaultDataSubscription == info.isDefaultDataSubscription
- && isDefaultSubscription == info.isDefaultSubscription
&& isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
}
@@ -303,8 +271,6 @@ public class SubscriptionInfoEntity {
.append(formattedPhoneNumber)
.append(", isFirstRemovableSubscription = ")
.append(isFirstRemovableSubscription)
- .append(", defaultSimConfig = ")
- .append(defaultSimConfig)
.append(", isDefaultSubscriptionSelection = ")
.append(isDefaultSubscriptionSelection)
.append(", isValidSubscription = ")
@@ -315,14 +281,6 @@ public class SubscriptionInfoEntity {
.append(isActiveSubscriptionId)
.append(", isAvailableSubscription = ")
.append(isAvailableSubscription)
- .append(", isDefaultVoiceSubscription = ")
- .append(isDefaultVoiceSubscription)
- .append(", isDefaultSmsSubscription = ")
- .append(isDefaultSmsSubscription)
- .append(", isDefaultDataSubscription = ")
- .append(isDefaultDataSubscription)
- .append(", isDefaultSubscription = ")
- .append(isDefaultSubscription)
.append(", isActiveDataSubscriptionId = ")
.append(isActiveDataSubscriptionId)
.append(")}");
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6312913d1403..59cd7a051fad 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -25,6 +25,8 @@
<!-- Comma-separated list of bluetooth, wifi, and cell. -->
<string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,uwb,wifi,wimax</string>
<string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi</string>
+ <string name="def_satellite_mode_radios" translatable="false"></string>
+ <integer name="def_satellite_mode_enabled" translatable="false">0</integer>
<string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
<bool name="def_auto_time">true</bool>
<bool name="def_auto_time_zone">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 74774167caa0..ed5654d4f259 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2413,6 +2413,12 @@ class DatabaseHelper extends SQLiteOpenHelper {
loadStringSetting(stmt, Settings.Global.AIRPLANE_MODE_RADIOS,
R.string.def_airplane_mode_radios);
+ loadStringSetting(stmt, Global.SATELLITE_MODE_RADIOS,
+ R.string.def_satellite_mode_radios);
+
+ loadIntegerSetting(stmt, Global.SATELLITE_MODE_ENABLED,
+ R.integer.def_satellite_mode_enabled);
+
loadStringSetting(stmt, Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
R.string.airplane_mode_toggleable_radios);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e736253dc90f..5a8c59489ec8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2311,7 +2311,7 @@ public class SettingsProvider extends ContentProvider {
@NonNull Set<String> flags) {
boolean hasAllowlistPermission =
context.checkCallingOrSelfPermission(
- Manifest.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG)
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG)
== PackageManager.PERMISSION_GRANTED;
boolean hasWritePermission =
context.checkCallingOrSelfPermission(
@@ -2331,7 +2331,7 @@ public class SettingsProvider extends ContentProvider {
}
} else {
throw new SecurityException("Permission denial to mutate flag, must have root, "
- + "WRITE_DEVICE_CONFIG, or ALLOWLISTED_WRITE_DEVICE_CONFIG");
+ + "WRITE_DEVICE_CONFIG, or WRITE_ALLOWLISTED_DEVICE_CONFIG");
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f2f0fe987f36..19f1a86ec90c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -273,7 +273,6 @@ public class SettingsBackupTest {
Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
- Settings.Global.STYLUS_HANDWRITING_ENABLED,
Settings.Global.STYLUS_EVER_USED,
Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
@@ -785,6 +784,7 @@ public class SettingsBackupTest {
Settings.Secure.SMS_DEFAULT_APPLICATION,
Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q
Settings.Secure.STYLUS_BUTTONS_ENABLED,
+ Settings.Secure.STYLUS_HANDWRITING_ENABLED,
Settings.Secure.TRUST_AGENTS_INITIALIZED,
Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4c48f0e63b16..fedfb43535cc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -148,7 +148,7 @@
<uses-permission android:name="android.permission.LOCATION_BYPASS" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
- <uses-permission android:name="android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index db88b593e432..204bac88bc0d 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -23,6 +23,8 @@ import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.cam.Cam
import com.android.internal.graphics.cam.CamUtils
import kotlin.math.absoluteValue
+import kotlin.math.max
+import kotlin.math.min
import kotlin.math.roundToInt
const val TAG = "ColorScheme"
@@ -35,12 +37,12 @@ internal interface Hue {
fun get(sourceColor: Cam): Double
/**
- * Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the
- * hue fall betweens, and use the hue rotation of the lower hue.
+ * Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the hue
+ * fall betweens, and use the hue rotation of the lower hue.
*
* @param sourceHue hue of source color
- * @param hueAndRotations list of pairs, where the first item in a pair is a hue, and the
- * second item in the pair is a hue rotation that should be applied
+ * @param hueAndRotations list of pairs, where the first item in a pair is a hue, and the second
+ * item in the pair is a hue rotation that should be applied
*/
fun getHueRotation(sourceHue: Float, hueAndRotations: List<Pair<Int, Int>>): Double {
val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat()
@@ -48,8 +50,9 @@ internal interface Hue {
val thisHue = hueAndRotations[i].first.toFloat()
val nextHue = hueAndRotations[i + 1].first.toFloat()
if (thisHue <= sanitizedSourceHue && sanitizedSourceHue < nextHue) {
- return ColorScheme.wrapDegreesDouble(sanitizedSourceHue.toDouble() +
- hueAndRotations[i].second)
+ return ColorScheme.wrapDegreesDouble(
+ sanitizedSourceHue.toDouble() + hueAndRotations[i].second
+ )
}
}
@@ -78,8 +81,18 @@ internal class HueSubtract(val amountDegrees: Double) : Hue {
}
internal class HueVibrantSecondary() : Hue {
- val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12),
- Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12))
+ val hueToRotations =
+ listOf(
+ Pair(0, 18),
+ Pair(41, 15),
+ Pair(61, 10),
+ Pair(101, 12),
+ Pair(131, 15),
+ Pair(181, 18),
+ Pair(251, 15),
+ Pair(301, 12),
+ Pair(360, 12)
+ )
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
@@ -87,8 +100,18 @@ internal class HueVibrantSecondary() : Hue {
}
internal class HueVibrantTertiary() : Hue {
- val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25),
- Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25))
+ val hueToRotations =
+ listOf(
+ Pair(0, 35),
+ Pair(41, 30),
+ Pair(61, 20),
+ Pair(101, 25),
+ Pair(131, 30),
+ Pair(181, 35),
+ Pair(251, 30),
+ Pair(301, 25),
+ Pair(360, 25)
+ )
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
@@ -96,8 +119,18 @@ internal class HueVibrantTertiary() : Hue {
}
internal class HueExpressiveSecondary() : Hue {
- val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
- Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45))
+ val hueToRotations =
+ listOf(
+ Pair(0, 45),
+ Pair(21, 95),
+ Pair(51, 45),
+ Pair(121, 20),
+ Pair(151, 45),
+ Pair(191, 90),
+ Pair(271, 45),
+ Pair(321, 45),
+ Pair(360, 45)
+ )
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
@@ -105,8 +138,18 @@ internal class HueExpressiveSecondary() : Hue {
}
internal class HueExpressiveTertiary() : Hue {
- val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
- Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120))
+ val hueToRotations =
+ listOf(
+ Pair(0, 120),
+ Pair(21, 120),
+ Pair(51, 20),
+ Pair(121, 45),
+ Pair(151, 20),
+ Pair(191, 15),
+ Pair(271, 20),
+ Pair(321, 120),
+ Pair(360, 120)
+ )
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
@@ -115,13 +158,18 @@ internal class HueExpressiveTertiary() : Hue {
internal interface Chroma {
fun get(sourceColor: Cam): Double
+
+ companion object {
+ val MAX_VALUE = 120.0
+ val MIN_VALUE = 0.0
+ }
}
internal class ChromaMaxOut : Chroma {
override fun get(sourceColor: Cam): Double {
// Intentionally high. Gamut mapping from impossible HCT to sRGB will ensure that
// the maximum chroma is reached, even if lower than this constant.
- return 130.0
+ return Chroma.MAX_VALUE + 10.0
}
}
@@ -131,6 +179,23 @@ internal class ChromaMultiple(val multiple: Double) : Chroma {
}
}
+internal class ChromaAdd(val amount: Double) : Chroma {
+ override fun get(sourceColor: Cam): Double {
+ return sourceColor.chroma + amount
+ }
+}
+
+internal class ChromaBound(
+ val baseChroma: Chroma,
+ val minVal: Double,
+ val maxVal: Double,
+) : Chroma {
+ override fun get(sourceColor: Cam): Double {
+ val result = baseChroma.get(sourceColor)
+ return min(max(result, minVal), maxVal)
+ }
+}
+
internal class ChromaConstant(val chroma: Double) : Chroma {
override fun get(sourceColor: Cam): Double {
return chroma
@@ -149,109 +214,173 @@ internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
val chroma = chroma.get(sourceColor)
return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
}
+
+ fun getAtTone(sourceColor: Cam, tone: Float): Int {
+ val hue = hue.get(sourceColor)
+ val chroma = chroma.get(sourceColor)
+ return ColorUtils.CAMToColor(hue.toFloat(), chroma.toFloat(), (1000f - tone) / 10f)
+ }
}
internal class CoreSpec(
- val a1: TonalSpec,
- val a2: TonalSpec,
- val a3: TonalSpec,
- val n1: TonalSpec,
- val n2: TonalSpec
+ val a1: TonalSpec,
+ val a2: TonalSpec,
+ val a3: TonalSpec,
+ val n1: TonalSpec,
+ val n2: TonalSpec
)
enum class Style(internal val coreSpec: CoreSpec) {
- SPRITZ(CoreSpec(
+ SPRITZ(
+ CoreSpec(
a1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(8.0)),
a3 = TonalSpec(HueSource(), ChromaConstant(16.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(2.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(2.0))
- )),
- TONAL_SPOT(CoreSpec(
+ )
+ ),
+ TONAL_SPOT(
+ CoreSpec(
a1 = TonalSpec(HueSource(), ChromaConstant(36.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(6.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
- )),
- VIBRANT(CoreSpec(
+ )
+ ),
+ VIBRANT(
+ CoreSpec(
a1 = TonalSpec(HueSource(), ChromaMaxOut()),
a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
- )),
- EXPRESSIVE(CoreSpec(
+ )
+ ),
+ EXPRESSIVE(
+ CoreSpec(
a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)),
a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)),
n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)),
n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0))
- )),
- RAINBOW(CoreSpec(
+ )
+ ),
+ RAINBOW(
+ CoreSpec(
a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
- )),
- FRUIT_SALAD(CoreSpec(
+ )
+ ),
+ FRUIT_SALAD(
+ CoreSpec(
a1 = TonalSpec(HueSubtract(50.0), ChromaConstant(48.0)),
a2 = TonalSpec(HueSubtract(50.0), ChromaConstant(36.0)),
a3 = TonalSpec(HueSource(), ChromaConstant(36.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(16.0))
- )),
- CONTENT(CoreSpec(
+ )
+ ),
+ CONTENT(
+ CoreSpec(
a1 = TonalSpec(HueSource(), ChromaSource()),
a2 = TonalSpec(HueSource(), ChromaMultiple(0.33)),
a3 = TonalSpec(HueSource(), ChromaMultiple(0.66)),
n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)),
n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666))
- )),
- MONOCHROMATIC(CoreSpec(
+ )
+ ),
+ MONOCHROMATIC(
+ CoreSpec(
a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
n2 = TonalSpec(HueSource(), ChromaConstant(.0))
- )),
+ )
+ ),
+ CLOCK(
+ CoreSpec(
+ a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 20.0, Chroma.MAX_VALUE)),
+ a2 = TonalSpec(HueAdd(10.0), ChromaBound(ChromaMultiple(0.85), 17.0, 40.0)),
+ a3 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaAdd(20.0), 50.0, Chroma.MAX_VALUE)),
+
+ // Not Used
+ n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
+ n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
+ )
+ ),
+ CLOCK_VIBRANT(
+ CoreSpec(
+ a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)),
+ a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)),
+ a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)),
+
+ // Not Used
+ n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
+ n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
+ )
+ )
}
-class TonalPalette {
- val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
- val allShades: List<Int>
- val allShadesMapped: Map<Int, Int>
+class TonalPalette
+internal constructor(
+ private val spec: TonalSpec,
+ seedColor: Int,
+) {
+ val seedCam: Cam = Cam.fromInt(seedColor)
+ val allShades: List<Int> = spec.shades(seedCam)
+ val allShadesMapped: Map<Int, Int> = SHADE_KEYS.zip(allShades).toMap()
val baseColor: Int
- internal constructor(spec: TonalSpec, seedColor: Int) {
- val seedCam = Cam.fromInt(seedColor)
- allShades = spec.shades(seedCam)
- allShadesMapped = shadeKeys.zip(allShades).toMap()
-
+ init {
val h = spec.hue.get(seedCam).toFloat()
val c = spec.chroma.get(seedCam).toFloat()
baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
}
- val s10: Int get() = this.allShades[0]
- val s50: Int get() = this.allShades[1]
- val s100: Int get() = this.allShades[2]
- val s200: Int get() = this.allShades[3]
- val s300: Int get() = this.allShades[4]
- val s400: Int get() = this.allShades[5]
- val s500: Int get() = this.allShades[6]
- val s600: Int get() = this.allShades[7]
- val s700: Int get() = this.allShades[8]
- val s800: Int get() = this.allShades[9]
- val s900: Int get() = this.allShades[10]
- val s1000: Int get() = this.allShades[11]
+ // Dynamically computed tones across the full range from 0 to 1000
+ fun getAtTone(tone: Float) = spec.getAtTone(seedCam, tone)
+
+ // Predefined & precomputed tones
+ val s10: Int
+ get() = this.allShades[0]
+ val s50: Int
+ get() = this.allShades[1]
+ val s100: Int
+ get() = this.allShades[2]
+ val s200: Int
+ get() = this.allShades[3]
+ val s300: Int
+ get() = this.allShades[4]
+ val s400: Int
+ get() = this.allShades[5]
+ val s500: Int
+ get() = this.allShades[6]
+ val s600: Int
+ get() = this.allShades[7]
+ val s700: Int
+ get() = this.allShades[8]
+ val s800: Int
+ get() = this.allShades[9]
+ val s900: Int
+ get() = this.allShades[10]
+ val s1000: Int
+ get() = this.allShades[11]
+
+ companion object {
+ val SHADE_KEYS = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+ }
}
class ColorScheme(
- @ColorInt val seed: Int,
- val darkTheme: Boolean,
- val style: Style = Style.TONAL_SPOT
+ @ColorInt val seed: Int,
+ val darkTheme: Boolean,
+ val style: Style = Style.TONAL_SPOT
) {
val accent1: TonalPalette
@@ -260,16 +389,14 @@ class ColorScheme(
val neutral1: TonalPalette
val neutral2: TonalPalette
- constructor(@ColorInt seed: Int, darkTheme: Boolean) :
- this(seed, darkTheme, Style.TONAL_SPOT)
+ constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT)
@JvmOverloads
constructor(
- wallpaperColors: WallpaperColors,
- darkTheme: Boolean,
- style: Style = Style.TONAL_SPOT
- ) :
- this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
+ wallpaperColors: WallpaperColors,
+ darkTheme: Boolean,
+ style: Style = Style.TONAL_SPOT
+ ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
val allHues: List<TonalPalette>
get() {
@@ -301,13 +428,14 @@ class ColorScheme(
init {
val proposedSeedCam = Cam.fromInt(seed)
- val seedArgb = if (seed == Color.TRANSPARENT) {
- GOOGLE_BLUE
- } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) {
- GOOGLE_BLUE
- } else {
- seed
- }
+ val seedArgb =
+ if (seed == Color.TRANSPARENT) {
+ GOOGLE_BLUE
+ } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) {
+ GOOGLE_BLUE
+ } else {
+ seed
+ }
accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
@@ -316,19 +444,23 @@ class ColorScheme(
neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
}
- val shadeCount get() = this.accent1.allShades.size
+ val shadeCount
+ get() = this.accent1.allShades.size
+
+ val seedTone: Float
+ get() = 1000f - CamUtils.lstarFromInt(seed) * 10f
override fun toString(): String {
return "ColorScheme {\n" +
- " seed color: ${stringForColor(seed)}\n" +
- " style: $style\n" +
- " palettes: \n" +
- " ${humanReadable("PRIMARY", accent1.allShades)}\n" +
- " ${humanReadable("SECONDARY", accent2.allShades)}\n" +
- " ${humanReadable("TERTIARY", accent3.allShades)}\n" +
- " ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
- " ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
- "}"
+ " seed color: ${stringForColor(seed)}\n" +
+ " style: $style\n" +
+ " palettes: \n" +
+ " ${humanReadable("PRIMARY", accent1.allShades)}\n" +
+ " ${humanReadable("SECONDARY", accent2.allShades)}\n" +
+ " ${humanReadable("TERTIARY", accent3.allShades)}\n" +
+ " ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
+ " ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
+ "}"
}
companion object {
@@ -356,8 +488,8 @@ class ColorScheme(
@JvmStatic
@JvmOverloads
fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List<Int> {
- val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }
- .toDouble()
+ val totalPopulation =
+ wallpaperColors.allColors.values.reduce { a, b -> a + b }.toDouble()
val totalPopulationMeaningless = (totalPopulation == 0.0)
if (totalPopulationMeaningless) {
// WallpaperColors with a population of 0 indicate the colors didn't come from
@@ -365,51 +497,56 @@ class ColorScheme(
// secondary/tertiary colors.
//
// In this case, the colors are usually from a Live Wallpaper.
- val distinctColors = wallpaperColors.mainColors.map {
- it.toArgb()
- }.distinct().filter {
- if (!filter) {
- true
- } else {
- Cam.fromInt(it).chroma >= MIN_CHROMA
- }
- }.toList()
+ val distinctColors =
+ wallpaperColors.mainColors
+ .map { it.toArgb() }
+ .distinct()
+ .filter {
+ if (!filter) {
+ true
+ } else {
+ Cam.fromInt(it).chroma >= MIN_CHROMA
+ }
+ }
+ .toList()
if (distinctColors.isEmpty()) {
return listOf(GOOGLE_BLUE)
}
return distinctColors
}
- val intToProportion = wallpaperColors.allColors.mapValues {
- it.value.toDouble() / totalPopulation
- }
+ val intToProportion =
+ wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation }
val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) }
// Get an array with 360 slots. A slot contains the percentage of colors with that hue.
val hueProportions = huePopulations(intToCam, intToProportion, filter)
// Map each color to the percentage of the image with its hue.
- val intToHueProportion = wallpaperColors.allColors.mapValues {
- val cam = intToCam[it.key]!!
- val hue = cam.hue.roundToInt()
- var proportion = 0.0
- for (i in hue - 15..hue + 15) {
- proportion += hueProportions[wrapDegrees(i)]
+ val intToHueProportion =
+ wallpaperColors.allColors.mapValues {
+ val cam = intToCam[it.key]!!
+ val hue = cam.hue.roundToInt()
+ var proportion = 0.0
+ for (i in hue - 15..hue + 15) {
+ proportion += hueProportions[wrapDegrees(i)]
+ }
+ proportion
}
- proportion
- }
// Remove any inappropriate seed colors. For example, low chroma colors look grayscale
// raising their chroma will turn them to a much louder color that may not have been
// in the image.
- val filteredIntToCam = if (!filter) intToCam else (intToCam.filter {
- val cam = it.value
- val proportion = intToHueProportion[it.key]!!
- cam.chroma >= MIN_CHROMA &&
- (totalPopulationMeaningless || proportion > 0.01)
- })
+ val filteredIntToCam =
+ if (!filter) intToCam
+ else
+ (intToCam.filter {
+ val cam = it.value
+ val proportion = intToHueProportion[it.key]!!
+ cam.chroma >= MIN_CHROMA &&
+ (totalPopulationMeaningless || proportion > 0.01)
+ })
// Sort the colors by score, from high to low.
- val intToScoreIntermediate = filteredIntToCam.mapValues {
- score(it.value, intToHueProportion[it.key]!!)
- }
+ val intToScoreIntermediate =
+ filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) }
val intToScore = intToScoreIntermediate.entries.toMutableList()
intToScore.sortByDescending { it.value }
@@ -423,11 +560,12 @@ class ColorScheme(
seeds.clear()
for (entry in intToScore) {
val int = entry.key
- val existingSeedNearby = seeds.find {
- val hueA = intToCam[int]!!.hue
- val hueB = intToCam[it]!!.hue
- hueDiff(hueA, hueB) < i
- } != null
+ val existingSeedNearby =
+ seeds.find {
+ val hueA = intToCam[int]!!.hue
+ val hueB = intToCam[it]!!.hue
+ hueDiff(hueA, hueB) < i
+ } != null
if (existingSeedNearby) {
continue
}
@@ -489,22 +627,22 @@ class ColorScheme(
}
private fun humanReadable(paletteName: String, colors: List<Int>): String {
- return "$paletteName\n" + colors.map {
- stringForColor(it)
- }.joinToString(separator = "\n") { it }
+ return "$paletteName\n" +
+ colors.map { stringForColor(it) }.joinToString(separator = "\n") { it }
}
private fun score(cam: Cam, proportion: Double): Double {
val proportionScore = 0.7 * 100.0 * proportion
- val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
- else 0.3 * (cam.chroma - ACCENT1_CHROMA)
+ val chromaScore =
+ if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
+ else 0.3 * (cam.chroma - ACCENT1_CHROMA)
return chromaScore + proportionScore
}
private fun huePopulations(
- camByColor: Map<Int, Cam>,
- populationByColor: Map<Int, Double>,
- filter: Boolean = true
+ camByColor: Map<Int, Cam>,
+ populationByColor: Map<Int, Double>,
+ filter: Boolean = true
): List<Double> {
val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 0080517a722b..c279053e6daf 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -109,6 +109,9 @@ interface ClockEvents {
/** Call whenever the locale changes */
fun onLocaleChanged(locale: Locale) {}
+ val isReactiveToTone
+ get() = true
+
/** Call whenever the color palette should update */
fun onColorPaletteChanged(resources: Resources) {}
@@ -164,8 +167,12 @@ interface ClockFaceEvents {
val hasCustomWeatherDataDisplay: Boolean
get() = false
- /** Region Darkness specific to the clock face */
- fun onRegionDarknessChanged(isDark: Boolean) {}
+ /**
+ * Region Darkness specific to the clock face.
+ * - isRegionDark = dark theme -> clock should be light
+ * - !isRegionDark = light theme -> clock should be dark
+ */
+ fun onRegionDarknessChanged(isRegionDark: Boolean) {}
/**
* Call whenever font settings change. Pass in a target font size in pixels. The specific clock
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index 8d4431520c75..befbfab7dbc3 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -37,4 +37,12 @@
<dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
<integer name="qs_carrier_max_em">7</integer>
+
+ <!-- Maximum number of notification icons shown on the Always on Display
+ (excluding overflow dot) -->
+ <integer name="max_notif_icons_on_aod">3</integer>
+ <!-- Maximum number of notification icons shown on the lockscreen (excluding overflow dot) -->
+ <integer name="max_notif_icons_on_lockscreen">3</integer>
+ <!-- Maximum number of notification icons shown in the status bar (excluding overflow dot) -->
+ <integer name="max_notif_static_icons">4</integer>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index ee9081c7027d..178cda46cdda 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
@@ -176,7 +177,9 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.textColorPrimary);
- mBgProtection.setImageDrawable(getContext().getDrawable(R.drawable.fingerprint_bg));
+ final int backgroundColor = Utils.getColorAttrDefaultColor(getContext(),
+ com.android.internal.R.attr.colorSurface);
+ mBgProtection.setImageTintList(ColorStateList.valueOf(backgroundColor));
mLockScreenFp.invalidate(); // updated with a valueCallback
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 99a10a33ab0f..37138114c740 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -44,7 +44,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.wm.shell.TaskViewFactory
+import com.android.wm.shell.taskview.TaskViewFactory
import java.util.Optional
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index c20af074c71e..d4ce9b699619 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -79,7 +79,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.asIndenting
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.indentIfPossible
-import com.android.wm.shell.TaskViewFactory
+import com.android.wm.shell.taskview.TaskViewFactory
import dagger.Lazy
import java.io.PrintWriter
import java.text.Collator
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 3d9eee4e9feb..5d608c3e3f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -36,7 +36,7 @@ import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.wm.shell.TaskView
+import com.android.wm.shell.taskview.TaskView
/**
* A dialog that provides an {@link TaskView}, allowing the application to provide
@@ -44,13 +44,13 @@ import com.android.wm.shell.TaskView
* The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}.
*/
class DetailDialog(
- val activityContext: Context,
- val broadcastSender: BroadcastSender,
- val taskView: TaskView,
- val pendingIntent: PendingIntent,
- val cvh: ControlViewHolder,
- val keyguardStateController: KeyguardStateController,
- val activityStarter: ActivityStarter
+ val activityContext: Context,
+ val broadcastSender: BroadcastSender,
+ val taskView: TaskView,
+ val pendingIntent: PendingIntent,
+ val cvh: ControlViewHolder,
+ val keyguardStateController: KeyguardStateController,
+ val activityStarter: ActivityStarter
) : Dialog(
activityContext,
R.style.Theme_SystemUI_Dialog_Control_DetailPanel
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 1f89c917186a..9a231814a813 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -30,7 +30,7 @@ import android.graphics.drawable.shapes.RoundRectShape
import android.os.Trace
import com.android.systemui.R
import com.android.systemui.util.boundsOnScreen
-import com.android.wm.shell.TaskView
+import com.android.wm.shell.taskview.TaskView
import java.util.concurrent.Executor
class PanelTaskViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 625a02801392..1a0fcea6ca87 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -38,7 +38,6 @@ import com.android.systemui.unfold.UnfoldLatencyTracker;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
-import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -49,16 +48,17 @@ import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
+import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.transition.ShellTransitions;
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
import java.util.Map;
import java.util.Optional;
import javax.inject.Provider;
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
/**
* An example Dagger Subcomponent for Core SysUI.
*
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index d756f3a44655..17d2332a4dac 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,7 +23,6 @@ import androidx.annotation.Nullable;
import com.android.systemui.SystemUIInitializerFactory;
import com.android.systemui.tv.TvWMComponent;
-import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.annotations.ShellMainThread;
@@ -38,13 +37,14 @@ import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
+import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.transition.ShellTransitions;
-import java.util.Optional;
-
import dagger.BindsInstance;
import dagger.Subcomponent;
+import java.util.Optional;
+
/**
* Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
* from the WM component into the SysUI component (in
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7ffb59422bc4..c7b4edb31b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -635,7 +635,7 @@ object Flags {
// TODO(b/269132640): Tracking Bug
@JvmField
val APP_PANELS_REMOVE_APPS_ALLOWED =
- unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = false)
+ unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true)
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
@@ -701,6 +701,6 @@ object Flags {
// TODO(b/272805037): Tracking Bug
@JvmField
- val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature",
- namespace = "vpn")
+ val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
+ namespace = "vpn", teamfood = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 77541e931e08..33f4e2e24322 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -21,8 +21,6 @@ import android.content.res.ColorStateList
import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
-import android.os.UserHandle
-import android.os.UserManager
import android.util.Log
import android.view.View
import com.android.keyguard.KeyguardConstants
@@ -106,10 +104,9 @@ constructor(
val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
/** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
val bouncerExpansion: Flow<Float> =
- combine(
- repository.panelExpansionAmount,
- repository.primaryBouncerShow
- ) { panelExpansion, primaryBouncerIsShowing ->
+ combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
+ panelExpansion,
+ primaryBouncerIsShowing ->
if (primaryBouncerIsShowing) {
1f - panelExpansion
} else {
@@ -195,6 +192,7 @@ constructor(
dismissCallbackRegistry.notifyDismissCancelled()
}
+ repository.setPrimaryStartDisappearAnimation(null)
falsingCollector.onBouncerHidden()
keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
cancelShowRunnable()
@@ -306,11 +304,8 @@ constructor(
runnable.run()
return
}
- val finishRunnable = Runnable {
- runnable.run()
- repository.setPrimaryStartDisappearAnimation(null)
- }
- repository.setPrimaryStartDisappearAnimation(finishRunnable)
+
+ repository.setPrimaryStartDisappearAnimation(runnable)
}
/** Determine whether to show the side fps animation. */
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 2d7861be2da9..f5c0a94d07f2 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -174,21 +174,26 @@ constructor(
infoReference.set(info)
- // TODO(b/266686199): We should handle when app not available. For now, we log.
- val intent = createNoteTaskIntent(info)
try {
+ // TODO(b/266686199): We should handle when app not available. For now, we log.
logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
when (info.launchMode) {
is NoteTaskLaunchMode.AppBubble -> {
// TODO: provide app bubble icon
+ val intent = createNoteTaskIntent(info)
bubbles.showOrHideAppBubble(intent, user, null /* icon */)
// App bubble logging happens on `onBubbleExpandChanged`.
logDebug { "onShowNoteTask - opened as app bubble: $info" }
}
is NoteTaskLaunchMode.Activity -> {
if (activityManager.isInForeground(info.packageName)) {
- logDebug { "onShowNoteTask - already opened as activity: $info" }
+ // Force note task into background by calling home.
+ val intent = createHomeIntent()
+ context.startActivityAsUser(intent, user)
+ eventLogger.logNoteTaskClosed(info)
+ logDebug { "onShowNoteTask - closed as activity: $info" }
} else {
+ val intent = createNoteTaskIntent(info)
context.startActivityAsUser(intent, user)
eventLogger.logNoteTaskOpened(info)
logDebug { "onShowNoteTask - opened as activity: $info" }
@@ -199,7 +204,7 @@ constructor(
} catch (e: ActivityNotFoundException) {
logDebug { "onShowNoteTask - failed: $info" }
}
- logDebug { "onShowNoteTask - compoleted: $info" }
+ logDebug { "onShowNoteTask - completed: $info" }
}
/**
@@ -306,3 +311,10 @@ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent =
private inline fun Any.logDebug(message: () -> String) {
if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
}
+
+/** Creates an [Intent] which forces the current app to background by calling home. */
+private fun createHomeIntent(): Intent =
+ Intent(Intent.ACTION_MAIN).apply {
+ addCategory(Intent.CATEGORY_HOME)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 9ece72d2ca7f..6be74a0b5646 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -40,13 +40,12 @@ public interface QSHost extends PanelInteractor {
/**
* Returns the default QS tiles for the context.
- * @param context the context to obtain the resources from
+ * @param res the resources to use to determine the default tiles
* @return a list of specs of the default tiles
*/
- static List<String> getDefaultSpecs(Context context) {
+ static List<String> getDefaultSpecs(Resources res) {
final ArrayList<String> tiles = new ArrayList();
- final Resources res = context.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ead97976ad9..8bbdeeda356c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -600,7 +600,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
if (tile.isEmpty()) continue;
if (tile.equals("default")) {
if (!addedDefault) {
- List<String> defaultSpecs = QSHost.getDefaultSpecs(context);
+ List<String> defaultSpecs = QSHost.getDefaultSpecs(context.getResources());
for (String spec : defaultSpecs) {
if (!addedSpecs.contains(spec)) {
tiles.add(spec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index a319fb8d8756..4002ac3aa120 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -175,7 +175,7 @@ public class QSCustomizerController extends ViewController<QSCustomizer> {
private void reset() {
- mTileAdapter.resetTileSpecs(QSHost.getDefaultSpecs(getContext()));
+ mTileAdapter.resetTileSpecs(QSHost.getDefaultSpecs(getContext().getResources()));
}
public boolean isCustomizing() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index cfe93132c044..dffe7fd5f818 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -29,6 +29,7 @@ import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.external.QSExternalModule;
+import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -40,14 +41,14 @@ import com.android.systemui.statusbar.policy.SafetyController;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
-import java.util.Map;
-
-import javax.inject.Named;
-
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
+import java.util.Map;
+
+import javax.inject.Named;
+
/**
* Module for QS dependencies
*/
@@ -56,7 +57,8 @@ import dagger.multibindings.Multibinds;
MediaModule.class,
QSExternalModule.class,
QSFlagsModule.class,
- QSHostModule.class
+ QSHostModule.class,
+ QSPipelineModule.class,
}
)
public interface QSModule {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
new file mode 100644
index 000000000000..00f0a67dbe22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
+import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class QSPipelineModule {
+
+ /** Implementation for [TileSpecRepository] */
+ @Binds
+ abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
+
+ @Binds
+ @IntoMap
+ @ClassKey(PrototypeCoreStartable::class)
+ abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
+
+ companion object {
+ /**
+ * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
+ * the list of current tiles.
+ */
+ @Provides
+ @SysUISingleton
+ @QSTileListLog
+ fun provideQSTileListLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create(QSPipelineLogger.TILE_LIST_TAG, maxSize = 700, systrace = false)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
new file mode 100644
index 000000000000..ad8bfeabc676
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.pipeline.dagger
+
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+/** A {@link LogBuffer} for the new QS Pipeline for logging changes to the set of current tiles. */
+@Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
new file mode 100644
index 000000000000..d254e1b3d0d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.annotation.UserIdInt
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Repository that tracks the current tiles. */
+interface TileSpecRepository {
+
+ /**
+ * Returns a flow of the current list of [TileSpec] for a given [userId].
+ *
+ * Tiles will never be [TileSpec.Invalid] in the list and it will never be empty.
+ */
+ fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>>
+
+ /**
+ * Adds a [tile] for a given [userId] at [position]. Using [POSITION_AT_END] will add the tile
+ * at the end of the list.
+ *
+ * Passing [TileSpec.Invalid] is a noop.
+ */
+ suspend fun addTile(@UserIdInt userId: Int, tile: TileSpec, position: Int = POSITION_AT_END)
+
+ /**
+ * Removes a [tile] for a given [userId].
+ *
+ * Passing [TileSpec.Invalid] or a non present tile is a noop.
+ */
+ suspend fun removeTile(@UserIdInt userId: Int, tile: TileSpec)
+
+ /**
+ * Sets the list of current [tiles] for a given [userId].
+ *
+ * [TileSpec.Invalid] will be ignored, and an effectively empty list will not be stored.
+ */
+ suspend fun setTiles(@UserIdInt userId: Int, tiles: List<TileSpec>)
+
+ companion object {
+ /** Position to indicate the end of the list */
+ const val POSITION_AT_END = -1
+ }
+}
+
+/**
+ * Implementation of [TileSpecRepository] that persist the values of tiles in
+ * [Settings.Secure.QS_TILES].
+ *
+ * All operations against [Settings] will be performed in a background thread.
+ */
+@SysUISingleton
+class TileSpecSettingsRepository
+@Inject
+constructor(
+ private val secureSettings: SecureSettings,
+ @Main private val resources: Resources,
+ private val logger: QSPipelineLogger,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TileSpecRepository {
+ override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ return conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ secureSettings.registerContentObserverForUser(SETTING, observer, userId)
+
+ awaitClose { secureSettings.unregisterContentObserver(observer) }
+ }
+ .onStart { emit(Unit) }
+ .map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
+ .onEach { logger.logTilesChangedInSettings(it, userId) }
+ .map { parseTileSpecs(it, userId) }
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
+ if (tile == TileSpec.Invalid) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tile !in tilesList) {
+ if (position < 0) {
+ tilesList.add(tile)
+ } else {
+ tilesList.add(position, tile)
+ }
+ storeTiles(userId, tilesList)
+ }
+ }
+
+ override suspend fun removeTile(userId: Int, tile: TileSpec) {
+ if (tile == TileSpec.Invalid) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tilesList.remove(tile)) {
+ storeTiles(userId, tilesList.toList())
+ }
+ }
+
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
+ val filtered = tiles.filter { it != TileSpec.Invalid }
+ if (filtered.isNotEmpty()) {
+ storeTiles(userId, filtered)
+ }
+ }
+
+ private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
+ return withContext(backgroundDispatcher) {
+ (secureSettings.getStringForUser(SETTING, forUser) ?: "")
+ .split(DELIMITER)
+ .map(TileSpec::create)
+ .filter { it !is TileSpec.Invalid }
+ }
+ }
+
+ private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+ val toStore =
+ tiles
+ .filter { it !is TileSpec.Invalid }
+ .joinToString(DELIMITER, transform = TileSpec::spec)
+ withContext(backgroundDispatcher) {
+ secureSettings.putStringForUser(
+ SETTING,
+ toStore,
+ null,
+ false,
+ forUser,
+ true,
+ )
+ }
+ }
+
+ private fun parseTileSpecs(tilesFromSettings: String, user: Int): List<TileSpec> {
+ val fromSettings =
+ tilesFromSettings.split(DELIMITER).map(TileSpec::create).filter {
+ it != TileSpec.Invalid
+ }
+ return if (fromSettings.isNotEmpty()) {
+ fromSettings.also { logger.logParsedTiles(it, false, user) }
+ } else {
+ QSHost.getDefaultSpecs(resources)
+ .map(TileSpec::create)
+ .filter { it != TileSpec.Invalid }
+ .also { logger.logParsedTiles(it, true, user) }
+ }
+ }
+
+ companion object {
+ private const val SETTING = Settings.Secure.QS_TILES
+ private const val DELIMITER = ","
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
new file mode 100644
index 000000000000..69d8248a11f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.prototyping
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.user.data.repository.UserRepository
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Class for observing results while prototyping.
+ *
+ * The flows do their own logging, so we just need to make sure that they collect.
+ *
+ * This will be torn down together with the last of the new pipeline flags remaining here.
+ */
+// TODO(b/270385608)
+@SysUISingleton
+class PrototypeCoreStartable
+@Inject
+constructor(
+ private val tileSpecRepository: TileSpecRepository,
+ private val userRepository: UserRepository,
+ private val featureFlags: FeatureFlags,
+ @Application private val scope: CoroutineScope,
+ private val commandRegistry: CommandRegistry,
+) : CoreStartable {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ scope.launch {
+ userRepository.selectedUserInfo
+ .flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
+ .collect {}
+ }
+ commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
+ }
+ }
+
+ private inner class CommandExecutor : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.size < 2) {
+ pw.println("Error: needs at least two arguments")
+ return
+ }
+ val spec = TileSpec.create(args[1])
+ if (spec == TileSpec.Invalid) {
+ pw.println("Error: Invalid tile spec ${args[1]}")
+ }
+ if (args[0] == "add") {
+ performAdd(args, spec)
+ pw.println("Requested tile added")
+ } else if (args[0] == "remove") {
+ performRemove(args, spec)
+ pw.println("Requested tile removed")
+ } else {
+ pw.println("Error: unknown command")
+ }
+ }
+
+ private fun performAdd(args: List<String>, spec: TileSpec) {
+ val position = args.getOrNull(2)?.toInt() ?: TileSpecRepository.POSITION_AT_END
+ val user = args.getOrNull(3)?.toInt() ?: userRepository.getSelectedUserInfo().id
+ scope.launch { tileSpecRepository.addTile(user, spec, position) }
+ }
+
+ private fun performRemove(args: List<String>, spec: TileSpec) {
+ val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
+ scope.launch { tileSpecRepository.removeTile(user, spec) }
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar $COMMAND:")
+ pw.println(" add <spec> [position] [user]")
+ pw.println(" remove <spec> [user]")
+ }
+ }
+
+ companion object {
+ private const val COMMAND = "qs-pipeline"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
new file mode 100644
index 000000000000..c691c2f668ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared
+
+import android.content.ComponentName
+import android.text.TextUtils
+import com.android.systemui.qs.external.CustomTile
+
+/**
+ * Container for the spec that identifies a tile.
+ *
+ * A tile's [spec] is one of two options:
+ * * `custom(<componentName>)`: A [ComponentName] surrounded by [CustomTile.PREFIX] and terminated
+ * by `)`, represents a tile provided by an app, corresponding to a `TileService`.
+ * * a string not starting with [CustomTile.PREFIX], representing a tile provided by SystemUI.
+ */
+sealed class TileSpec private constructor(open val spec: String) {
+
+ /** Represents a spec that couldn't be parsed into a valid type of tile. */
+ object Invalid : TileSpec("") {
+ override fun toString(): String {
+ return "TileSpec.INVALID"
+ }
+ }
+
+ /** Container for the spec of a tile provided by SystemUI. */
+ data class PlatformTileSpec
+ internal constructor(
+ override val spec: String,
+ ) : TileSpec(spec)
+
+ /**
+ * Container for the spec of a tile provided by an app.
+ *
+ * [componentName] indicates the associated `TileService`.
+ */
+ data class CustomTileSpec
+ internal constructor(
+ override val spec: String,
+ val componentName: ComponentName,
+ ) : TileSpec(spec)
+
+ companion object {
+ /** Create a [TileSpec] from the string [spec]. */
+ fun create(spec: String): TileSpec {
+ return if (TextUtils.isEmpty(spec)) {
+ Invalid
+ } else if (!spec.isCustomTileSpec) {
+ PlatformTileSpec(spec)
+ } else {
+ spec.componentName?.let { CustomTileSpec(spec, it) } ?: Invalid
+ }
+ }
+
+ private val String.isCustomTileSpec: Boolean
+ get() = startsWith(CustomTile.PREFIX)
+
+ private val String.componentName: ComponentName?
+ get() =
+ if (!isCustomTileSpec) {
+ null
+ } else {
+ if (endsWith(")")) {
+ val extracted = substring(CustomTile.PREFIX.length, length - 1)
+ ComponentName.unflattenFromString(extracted)
+ } else {
+ null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
new file mode 100644
index 000000000000..200f7431e906
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared.logging
+
+import android.annotation.UserIdInt
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.qs.pipeline.dagger.QSTileListLog
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/**
+ * Logger for the new pipeline.
+ *
+ * This may log to different buffers depending of the function of the log.
+ */
+class QSPipelineLogger
+@Inject
+constructor(
+ @QSTileListLog private val tileListLogBuffer: LogBuffer,
+) {
+
+ companion object {
+ const val TILE_LIST_TAG = "QSTileListLog"
+ }
+
+ /**
+ * Log the tiles that are parsed in the repo. This is effectively what is surfaces in the flow.
+ *
+ * [usesDefault] indicates if the default tiles were used (due to the setting being empty or
+ * invalid).
+ */
+ fun logParsedTiles(tiles: List<TileSpec>, usesDefault: Boolean, user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tiles.toString()
+ bool1 = usesDefault
+ int1 = user
+ },
+ { "Parsed tiles (default=$bool1, user=$int1): $str1" }
+ )
+ }
+
+ /**
+ * Logs when the tiles change in Settings.
+ *
+ * This could be caused by SystemUI, or restore.
+ */
+ fun logTilesChangedInSettings(newTiles: String, @UserIdInt user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = newTiles
+ int1 = user
+ },
+ { "Tiles changed in settings for user $int1: $str1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 80eea81a541d..1b83397b1afb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -788,7 +788,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private void disconnectFromLauncherService(String disconnectReason) {
Log.d(TAG_OPS, "disconnectFromLauncherService bound?: " + mBound +
- " currentProxy: " + mOverviewProxy + " disconnectReason: " + disconnectReason);
+ " currentProxy: " + mOverviewProxy + " disconnectReason: " + disconnectReason,
+ new Throwable());
if (mBound) {
// Always unbind the service (ie. if called through onNullBinding or onBindingDied)
mContext.unbindService(mOverviewServiceConnection);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index efd79d737f71..3227ef47f733 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -190,9 +190,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
} catch (Exception e) {
// IOException/UnsupportedOperationException may be thrown if external storage is
// not mounted
- if (DEBUG_STORAGE) {
- Log.d(TAG, "Failed to store screenshot", e);
- }
+ Log.d(TAG, "Failed to store screenshot", e);
mParams.clearImage();
mImageData.reset();
mQuickShareData.reset();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b2ae4a021f2c..d51a97f2ac52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -1119,9 +1119,7 @@ public class ScreenshotController {
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "finishDismiss");
- }
+ Log.d(TAG, "finishDismiss");
if (mLastScrollCaptureRequest != null) {
mLastScrollCaptureRequest.cancel(true);
mLastScrollCaptureRequest = null;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 7ac0fd50ea33..f3d2828072be 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -253,6 +253,7 @@ public class TakeScreenshotService extends Service {
Consumer<Uri> uriConsumer, RequestCallback callback) {
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
screenshot.getPackageNameString());
+ Log.d(TAG, "Screenshot request: " + screenshot);
mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 0b2ae05b7c9b..72286f175671 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -161,6 +161,10 @@ open class UserTrackerImpl internal constructor(
private fun registerUserSwitchObserver() {
iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
+ override fun onBeforeUserSwitching(newUserId: Int) {
+ setUserIdInternal(newUserId)
+ }
+
override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
backgroundHandler.run {
handleUserSwitching(newUserId)
@@ -181,8 +185,6 @@ open class UserTrackerImpl internal constructor(
Assert.isNotMainThread()
Log.i(TAG, "Switching to user $newUserId")
- setUserIdInternal(newUserId)
-
val list = synchronized(callbacks) {
callbacks.toList()
}
@@ -205,7 +207,6 @@ open class UserTrackerImpl internal constructor(
Assert.isNotMainThread()
Log.i(TAG, "Switched to user $newUserId")
- setUserIdInternal(newUserId)
notifySubscribers {
onUserChanged(newUserId, userContext)
onProfilesChanged(userProfiles)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index c9f31bad74c0..8aeefeeac211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -307,7 +307,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
}
entriesToLocallyDismiss.add(entry);
- if (!isCanceled(entry)) {
+ if (!entry.isCanceled()) {
// send message to system server if this notification hasn't already been cancelled
mBgExecutor.execute(() -> {
try {
@@ -387,7 +387,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
entry.setDismissState(DISMISSED);
mLogger.logNotifDismissed(entry);
- if (isCanceled(entry)) {
+ if (entry.isCanceled()) {
canceledEntries.add(entry);
} else {
// Mark any children as dismissed as system server will auto-dismiss them as well
@@ -396,7 +396,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
otherEntry.setDismissState(PARENT_DISMISSED);
mLogger.logChildDismissed(otherEntry);
- if (isCanceled(otherEntry)) {
+ if (otherEntry.isCanceled()) {
canceledEntries.add(otherEntry);
}
}
@@ -523,7 +523,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
+ logKey(entry)));
}
- if (!isCanceled(entry)) {
+ if (!entry.isCanceled()) {
throw mEulogizer.record(
new IllegalStateException("Cannot remove notification " + logKey(entry)
+ ": has not been marked for removal"));
@@ -587,7 +587,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
private void applyRanking(@NonNull RankingMap rankingMap) {
ArrayMap<String, NotificationEntry> currentEntriesWithoutRankings = null;
for (NotificationEntry entry : mNotificationSet.values()) {
- if (!isCanceled(entry)) {
+ if (!entry.isCanceled()) {
// TODO: (b/148791039) We should crash if we are ever handed a ranking with
// incomplete entries. Right now, there's a race condition in NotificationListener
@@ -815,15 +815,6 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
return ranking;
}
- /**
- * True if the notification has been canceled by system server. Usually, such notifications are
- * immediately removed from the collection, but can sometimes stick around due to lifetime
- * extenders.
- */
- private boolean isCanceled(NotificationEntry entry) {
- return entry.mCancellationReason != REASON_NOT_CANCELED;
- }
-
private boolean cannotBeLifetimeExtended(NotificationEntry entry) {
final boolean locallyDismissedByUser = entry.getDismissState() != NOT_DISMISSED;
final boolean systemServerReportedUserCancel =
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 3399f9df7fd5..f7790e861e27 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
@@ -321,6 +321,15 @@ public final class NotificationEntry extends ListEntry {
mDismissState = requireNonNull(dismissState);
}
+ /**
+ * True if the notification has been canceled by system server. Usually, such notifications are
+ * immediately removed from the collection, but can sometimes stick around due to lifetime
+ * extenders.
+ */
+ public boolean isCanceled() {
+ return mCancellationReason != REASON_NOT_CANCELED;
+ }
+
@Nullable public NotifFilter getExcludingFilter() {
return getAttachState().getExcludingFilter();
}
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 0d9a654fa485..058545689c01 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
@@ -46,7 +46,7 @@ class NotifUiAdjustmentProvider @Inject constructor(
private val userTracker: UserTracker
) {
private val dirtyListeners = ListenerSet<Runnable>()
- private var isSnoozeEnabled = false
+ private var isSnoozeSettingsEnabled = false
/**
* Update the snooze enabled value on user switch
@@ -95,7 +95,7 @@ class NotifUiAdjustmentProvider @Inject constructor(
}
private fun updateSnoozeEnabled() {
- isSnoozeEnabled =
+ isSnoozeSettingsEnabled =
secureSettings.getIntForUser(SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1
}
@@ -118,7 +118,7 @@ class NotifUiAdjustmentProvider @Inject constructor(
smartActions = entry.ranking.smartActions,
smartReplies = entry.ranking.smartReplies,
isConversation = entry.ranking.isConversation,
- isSnoozeEnabled = isSnoozeEnabled,
+ isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
isMinimized = isEntryMinimized(entry),
needsRedaction = lockscreenUserManager.needsRedaction(entry),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 4ee2de11abdf..006a029de8e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -136,11 +136,13 @@ public class NotificationIconContainer extends ViewGroup {
}
}.setDuration(CONTENT_FADE_DURATION);
- private static final int MAX_ICONS_ON_AOD = 3;
+ /* Maximum number of icons on AOD when also showing overflow dot. */
+ private int mMaxIconsOnAod;
/* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */
- public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
- public static final int MAX_STATIC_ICONS = 4;
+ private int mMaxIconsOnLockscreen;
+ /* Maximum number of icons in the status bar when also showing overflow dot. */
+ private int mMaxStaticIcons;
private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
@@ -174,14 +176,19 @@ public class NotificationIconContainer extends ViewGroup {
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
- initDimens();
+ initResources();
setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW));
}
- private void initDimens() {
+ private void initResources() {
+ mMaxIconsOnAod = getResources().getInteger(R.integer.max_notif_icons_on_aod);
+ mMaxIconsOnLockscreen = getResources().getInteger(R.integer.max_notif_icons_on_lockscreen);
+ mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons);
+
mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
mStaticDotDiameter = 2 * mStaticDotRadius;
+
final Context themedContext = new ContextThemeWrapper(getContext(),
com.android.internal.R.style.Theme_DeviceDefault_DayNight);
mThemedTextColorPrimary = Utils.getColorAttr(themedContext,
@@ -225,7 +232,7 @@ public class NotificationIconContainer extends ViewGroup {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- initDimens();
+ initResources();
}
@Override
@@ -424,7 +431,7 @@ public class NotificationIconContainer extends ViewGroup {
return 0f;
}
final float contentWidth =
- mIconSize * MathUtils.min(numIcons, MAX_ICONS_ON_LOCKSCREEN + 1);
+ mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1);
return getActualPaddingStart()
+ contentWidth
+ getActualPaddingEnd();
@@ -539,8 +546,8 @@ public class NotificationIconContainer extends ViewGroup {
}
private int getMaxVisibleIcons(int childCount) {
- return mOnLockScreen ? MAX_ICONS_ON_AOD :
- mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+ return mOnLockScreen ? mMaxIconsOnAod :
+ mIsStaticLayout ? mMaxStaticIcons : childCount;
}
private float getLayoutEnd() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 53e08ea8e10d..118bfc55dd4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -14,6 +14,7 @@ import android.view.WindowManager.fixScale
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.systemui.DejankUtils
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
@@ -27,6 +28,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -116,6 +118,11 @@ class UnlockedScreenOffAnimationController @Inject constructor(
})
}
+ // FrameCallback used to delay starting the light reveal animation until the next frame
+ private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") {
+ lightRevealAnimator.start()
+ }
+
val animatorDurationScaleObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
updateAnimatorDurationScale()
@@ -223,6 +230,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
decidedToAnimateGoingToSleep = null
shouldAnimateInKeyguard = false
+ DejankUtils.removeCallbacks(startLightRevealCallback)
lightRevealAnimator.cancel()
handler.removeCallbacksAndMessages(null)
}
@@ -253,7 +261,14 @@ class UnlockedScreenOffAnimationController @Inject constructor(
shouldAnimateInKeyguard = true
lightRevealAnimationPlaying = true
- lightRevealAnimator.start()
+
+ // Start the animation on the next frame. startAnimation() is called after
+ // PhoneWindowManager makes a binder call to System UI on
+ // IKeyguardService#onStartedGoingToSleep(). By the time we get here, system_server is
+ // already busy making changes to PowerManager and DisplayManager. This increases our
+ // chance of missing the first frame, so to mitigate this we should start the animation
+ // on the next frame.
+ DejankUtils.postAfterTraversal(startLightRevealCallback)
handler.postDelayed({
// Only run this callback if the device is sleeping (not interactive). This callback
// is removed in onStartedWakingUp, but since that event is asynchronously
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
index b311318fb111..64234c205617 100644
--- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -17,6 +17,7 @@
package com.android.systemui.util
import android.os.Trace
+import android.os.TraceNameSupplier
/**
* Run a block within a [Trace] section.
@@ -39,5 +40,16 @@ class TraceUtils {
inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
return Runnable { traceSection(tag) { block() } }
}
+
+ /**
+ * Helper function for creating a Runnable object that implements TraceNameSupplier.
+ * This is useful for posting Runnables to Handlers with meaningful names.
+ */
+ inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
+ return object : Runnable, TraceNameSupplier {
+ override fun getTraceName(): String = tag
+ override fun run() = block()
+ }
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 16fb50c15af6..38372a33f1e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -29,7 +29,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.wm.shell.TaskViewFactory
+import com.android.wm.shell.taskview.TaskViewFactory
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 845848071f54..605dc3f2e90a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -59,8 +59,8 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
-import com.android.wm.shell.TaskView
-import com.android.wm.shell.TaskViewFactory
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewFactory
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
index 6a6a65a601fb..c3506e80966b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -24,7 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.wm.shell.TaskView
+import com.android.wm.shell.taskview.TaskView
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index 9df7992f979f..f7c8ccaf731a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -36,7 +36,7 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
-import com.android.wm.shell.TaskView
+import com.android.wm.shell.taskview.TaskView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index bdc33f45c717..4c8a0a51bcdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -131,6 +131,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
verify(repository).setPrimaryShowingSoon(false)
verify(repository).setPrimaryShow(false)
verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
+ verify(repository).setPrimaryStartDisappearAnimation(null)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
index 91a6de6ae4c0..ea11f01ed580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.lifecycle
-import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.view.ViewTreeObserver
import androidx.lifecycle.Lifecycle
@@ -28,8 +27,16 @@ import com.android.systemui.util.Assert
import com.android.systemui.util.mockito.argumentCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -41,9 +48,9 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
-@RunWithLooper
class RepeatWhenAttachedTest : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -54,9 +61,13 @@ class RepeatWhenAttachedTest : SysuiTestCase() {
private lateinit var block: Block
private lateinit var attachListeners: MutableList<View.OnAttachStateChangeListener>
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ Dispatchers.setMain(testDispatcher)
Assert.setTestThread(Thread.currentThread())
whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
whenever(view.windowVisibility).thenReturn(View.GONE)
@@ -71,186 +82,218 @@ class RepeatWhenAttachedTest : SysuiTestCase() {
block = Block()
}
- @Test(expected = IllegalStateException::class)
- fun `repeatWhenAttached - enforces main thread`() = runBlockingTest {
- Assert.setTestThread(null)
-
- repeatWhenAttached()
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
}
@Test(expected = IllegalStateException::class)
- fun `repeatWhenAttached - dispose enforces main thread`() = runBlockingTest {
- val disposableHandle = repeatWhenAttached()
- Assert.setTestThread(null)
+ fun `repeatWhenAttached - enforces main thread`() =
+ testScope.runTest {
+ Assert.setTestThread(null)
- disposableHandle.dispose()
- }
-
- @Test
- fun `repeatWhenAttached - view starts detached - runs block when attached`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(false)
- repeatWhenAttached()
- assertThat(block.invocationCount).isEqualTo(0)
+ repeatWhenAttached()
+ }
- whenever(view.isAttachedToWindow).thenReturn(true)
- attachListeners.last().onViewAttachedToWindow(view)
+ @Test(expected = IllegalStateException::class)
+ fun `repeatWhenAttached - dispose enforces main thread`() =
+ testScope.runTest {
+ val disposableHandle = repeatWhenAttached()
+ Assert.setTestThread(null)
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
- }
+ disposableHandle.dispose()
+ }
@Test
- fun `repeatWhenAttached - view already attached - immediately runs block`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
-
- repeatWhenAttached()
-
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
- }
+ fun `repeatWhenAttached - view starts detached - runs block when attached`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ repeatWhenAttached()
+ assertThat(block.invocationCount).isEqualTo(0)
+
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ attachListeners.last().onViewAttachedToWindow(view)
+
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
@Test
- fun `repeatWhenAttached - starts visible without focus - STARTED`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ fun `repeatWhenAttached - view already attached - immediately runs block`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
- repeatWhenAttached()
+ repeatWhenAttached()
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
- }
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
@Test
- fun `repeatWhenAttached - starts with focus but invisible - CREATED`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- whenever(view.hasWindowFocus()).thenReturn(true)
+ fun `repeatWhenAttached - starts visible without focus - STARTED`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- repeatWhenAttached()
+ repeatWhenAttached()
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
- }
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+ }
@Test
- fun `repeatWhenAttached - starts visible and with focus - RESUMED`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- whenever(view.hasWindowFocus()).thenReturn(true)
+ fun `repeatWhenAttached - starts with focus but invisible - CREATED`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.hasWindowFocus()).thenReturn(true)
- repeatWhenAttached()
+ repeatWhenAttached()
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
- }
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
@Test
- fun `repeatWhenAttached - becomes visible without focus - STARTED`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- repeatWhenAttached()
- val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(listenerCaptor.capture())
+ fun `repeatWhenAttached - starts visible and with focus - RESUMED`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(true)
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- listenerCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+ repeatWhenAttached()
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
- }
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+ }
@Test
- fun `repeatWhenAttached - gains focus but invisible - CREATED`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- repeatWhenAttached()
- val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(listenerCaptor.capture())
-
- whenever(view.hasWindowFocus()).thenReturn(true)
- listenerCaptor.value.onWindowFocusChanged(true)
+ fun `repeatWhenAttached - becomes visible without focus - STARTED`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(listenerCaptor.capture())
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ listenerCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+ }
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
- }
+ @Test
+ fun `repeatWhenAttached - gains focus but invisible - CREATED`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(listenerCaptor.capture())
+
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ listenerCaptor.value.onWindowFocusChanged(true)
+
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
@Test
- fun `repeatWhenAttached - becomes visible and gains focus - RESUMED`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- repeatWhenAttached()
- val visibleCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(visibleCaptor.capture())
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(focusCaptor.capture())
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- visibleCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
- whenever(view.hasWindowFocus()).thenReturn(true)
- focusCaptor.value.onWindowFocusChanged(true)
-
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
- }
+ fun `repeatWhenAttached - becomes visible and gains focus - RESUMED`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val visibleCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(visibleCaptor.capture())
+ val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(focusCaptor.capture())
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ visibleCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ focusCaptor.value.onWindowFocusChanged(true)
+
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+ }
@Test
- fun `repeatWhenAttached - view gets detached - destroys the lifecycle`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- repeatWhenAttached()
+ fun `repeatWhenAttached - view gets detached - destroys the lifecycle`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
- whenever(view.isAttachedToWindow).thenReturn(false)
- attachListeners.last().onViewDetachedFromWindow(view)
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ attachListeners.last().onViewDetachedFromWindow(view)
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
- }
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
@Test
- fun `repeatWhenAttached - view gets reattached - recreates a lifecycle`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- repeatWhenAttached()
- whenever(view.isAttachedToWindow).thenReturn(false)
- attachListeners.last().onViewDetachedFromWindow(view)
-
- whenever(view.isAttachedToWindow).thenReturn(true)
- attachListeners.last().onViewAttachedToWindow(view)
-
- assertThat(block.invocationCount).isEqualTo(2)
- assertThat(block.invocations[0].lifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
- assertThat(block.invocations[1].lifecycleState).isEqualTo(Lifecycle.State.CREATED)
- }
+ fun `repeatWhenAttached - view gets reattached - recreates a lifecycle`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ attachListeners.last().onViewAttachedToWindow(view)
+
+ runCurrent()
+ assertThat(block.invocationCount).isEqualTo(2)
+ assertThat(block.invocations[0].lifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ assertThat(block.invocations[1].lifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
@Test
- fun `repeatWhenAttached - dispose attached`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val handle = repeatWhenAttached()
+ fun `repeatWhenAttached - dispose attached`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val handle = repeatWhenAttached()
- handle.dispose()
+ handle.dispose()
- assertThat(attachListeners).isEmpty()
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
- }
+ runCurrent()
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
@Test
- fun `repeatWhenAttached - dispose never attached`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(false)
- val handle = repeatWhenAttached()
+ fun `repeatWhenAttached - dispose never attached`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ val handle = repeatWhenAttached()
- handle.dispose()
+ handle.dispose()
- assertThat(attachListeners).isEmpty()
- assertThat(block.invocationCount).isEqualTo(0)
- }
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(0)
+ }
@Test
- fun `repeatWhenAttached - dispose previously attached now detached`() = runBlockingTest {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val handle = repeatWhenAttached()
- attachListeners.last().onViewDetachedFromWindow(view)
-
- handle.dispose()
-
- assertThat(attachListeners).isEmpty()
- assertThat(block.invocationCount).isEqualTo(1)
- assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
- }
+ fun `repeatWhenAttached - dispose previously attached now detached`() =
+ testScope.runTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val handle = repeatWhenAttached()
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ handle.dispose()
+
+ runCurrent()
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
private fun CoroutineScope.repeatWhenAttached(): DisposableHandle {
return view.repeatWhenAttached(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 9897ce106137..fbe089a0616f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -25,6 +25,8 @@ import android.app.role.RoleManager.ROLE_NOTES
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.ACTION_MAIN
+import android.content.Intent.CATEGORY_HOME
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@@ -278,7 +280,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldStartActivityAndLogUiEvent() {
+ fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldCloseActivityAndLogUiEvent() {
val expectedInfo =
NOTE_TASK_INFO.copy(
entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
@@ -291,8 +293,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
- verify(context, never()).startActivityAsUser(any(), any())
- verifyZeroInteractions(bubbles, eventLogger)
+ val intentCaptor = argumentCaptor<Intent>()
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(ACTION_MAIN)
+ assertThat(intent.categories).contains(CATEGORY_HOME)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ }
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
+ verify(eventLogger).logNoteTaskClosed(expectedInfo)
+ verifyZeroInteractions(bubbles)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
new file mode 100644
index 000000000000..c03849b35f54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class TileSpecSettingsRepositoryTest : SysuiTestCase() {
+
+ private lateinit var secureSettings: FakeSettings
+
+ @Mock private lateinit var logger: QSPipelineLogger
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var underTest: TileSpecSettingsRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ secureSettings = FakeSettings()
+
+ with(context.orCreateTestableResources) {
+ addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES)
+ }
+
+ underTest =
+ TileSpecSettingsRepository(
+ secureSettings,
+ context.resources,
+ logger,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun emptySetting_usesDefaultValue() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+ assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+ }
+
+ @Test
+ fun changeInSettings_changesValue() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("a", 0)
+ assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+
+ storeTilesForUser("a,custom(b/c)", 0)
+ assertThat(tiles)
+ .isEqualTo(listOf(TileSpec.create("a"), TileSpec.create("custom(b/c)")))
+ }
+
+ @Test
+ fun tilesForCorrectUsers() =
+ testScope.runTest {
+ val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0))
+ val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1))
+
+ val user0Tiles = "a"
+ val user1Tiles = "custom(b/c)"
+ storeTilesForUser(user0Tiles, 0)
+ storeTilesForUser(user1Tiles, 1)
+
+ assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTileSpecs())
+ assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTileSpecs())
+ }
+
+ @Test
+ fun invalidTilesAreNotPresent() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "d,custom(bad)"
+ storeTilesForUser(specs, 0)
+
+ assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid })
+ }
+
+ @Test
+ fun noValidTiles_defaultSet() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("custom(bad),custom()", 0)
+
+ assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+ }
+
+ @Test
+ fun addTileAtEnd() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("a", 0)
+
+ underTest.addTile(userId = 0, TileSpec.create("b"))
+
+ val expected = "a,b"
+ assertThat(loadTilesForUser(0)).isEqualTo(expected)
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ }
+
+ @Test
+ fun addTileAtPosition() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("a,custom(b/c)", 0)
+
+ underTest.addTile(userId = 0, TileSpec.create("d"), position = 1)
+
+ val expected = "a,d,custom(b/c)"
+ assertThat(loadTilesForUser(0)).isEqualTo(expected)
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ }
+
+ @Test
+ fun addInvalidTile_noop() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,custom(b/c)"
+ storeTilesForUser(specs, 0)
+
+ underTest.addTile(userId = 0, TileSpec.Invalid)
+
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ }
+
+ @Test
+ fun addTileForOtherUser_addedInThatUser() =
+ testScope.runTest {
+ val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
+ val tilesUser1 by collectLastValue(underTest.tilesSpecs(1))
+
+ storeTilesForUser("a", 0)
+ storeTilesForUser("b", 1)
+
+ underTest.addTile(userId = 1, TileSpec.create("c"))
+
+ assertThat(loadTilesForUser(0)).isEqualTo("a")
+ assertThat(tilesUser0).isEqualTo("a".toTileSpecs())
+ assertThat(loadTilesForUser(1)).isEqualTo("b,c")
+ assertThat(tilesUser1).isEqualTo("b,c".toTileSpecs())
+ }
+
+ @Test
+ fun removeTile() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("a,b", 0)
+
+ underTest.removeTile(userId = 0, TileSpec.create("a"))
+
+ assertThat(loadTilesForUser(0)).isEqualTo("b")
+ assertThat(tiles).isEqualTo("b".toTileSpecs())
+ }
+
+ @Test
+ fun removeTileNotThere_noop() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,b"
+ storeTilesForUser(specs, 0)
+
+ underTest.removeTile(userId = 0, TileSpec.create("c"))
+
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ }
+
+ @Test
+ fun removeInvalidTile_noop() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,b"
+ storeTilesForUser(specs, 0)
+
+ underTest.removeTile(userId = 0, TileSpec.Invalid)
+
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ }
+
+ @Test
+ fun removeTileFromSecondaryUser_removedOnlyInCorrectUser() =
+ testScope.runTest {
+ val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
+ val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
+
+ val specs = "a,b"
+ storeTilesForUser(specs, 0)
+ storeTilesForUser(specs, 1)
+
+ underTest.removeTile(userId = 1, TileSpec.create("a"))
+
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
+ assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
+ assertThat(loadTilesForUser(1)).isEqualTo("b")
+ assertThat(user1Tiles).isEqualTo("b".toTileSpecs())
+ }
+
+ @Test
+ fun changeTiles() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,custom(b/c)"
+
+ underTest.setTiles(userId = 0, specs.toTileSpecs())
+
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ }
+
+ @Test
+ fun changeTiles_ignoresInvalid() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,custom(b/c)"
+
+ underTest.setTiles(userId = 0, listOf(TileSpec.Invalid) + specs.toTileSpecs())
+
+ assertThat(loadTilesForUser(0)).isEqualTo(specs)
+ assertThat(tiles).isEqualTo(specs.toTileSpecs())
+ }
+
+ @Test
+ fun changeTiles_empty_noChanges() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ underTest.setTiles(userId = 0, emptyList())
+
+ assertThat(loadTilesForUser(0)).isNull()
+ assertThat(tiles).isEqualTo(getDefaultTileSpecs())
+ }
+
+ @Test
+ fun changeTiles_forCorrectUser() =
+ testScope.runTest {
+ val user0Tiles by collectLastValue(underTest.tilesSpecs(0))
+ val user1Tiles by collectLastValue(underTest.tilesSpecs(1))
+
+ val specs = "a"
+ storeTilesForUser(specs, 0)
+ storeTilesForUser(specs, 1)
+
+ underTest.setTiles(userId = 1, "b".toTileSpecs())
+
+ assertThat(loadTilesForUser(0)).isEqualTo("a")
+ assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
+
+ assertThat(loadTilesForUser(1)).isEqualTo("b")
+ assertThat(user1Tiles).isEqualTo("b".toTileSpecs())
+ }
+
+ @Test
+ fun multipleConcurrentRemovals_bothRemoved() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,b,c"
+ storeTilesForUser(specs, 0)
+
+ coroutineScope {
+ underTest.removeTile(userId = 0, TileSpec.create("c"))
+ underTest.removeTile(userId = 0, TileSpec.create("a"))
+ }
+
+ assertThat(loadTilesForUser(0)).isEqualTo("b")
+ assertThat(tiles).isEqualTo("b".toTileSpecs())
+ }
+
+ private fun getDefaultTileSpecs(): List<TileSpec> {
+ return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
+ }
+
+ private fun storeTilesForUser(specs: String, forUser: Int) {
+ secureSettings.putStringForUser(SETTING, specs, forUser)
+ }
+
+ private fun loadTilesForUser(forUser: Int): String? {
+ return secureSettings.getStringForUser(SETTING, forUser)
+ }
+
+ companion object {
+ private const val DEFAULT_TILES = "a,b,c"
+ private const val SETTING = Settings.Secure.QS_TILES
+
+ private fun String.toTileSpecs(): List<TileSpec> {
+ return split(",").map(TileSpec::create)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
new file mode 100644
index 000000000000..d880172c1ba6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TileSpecTest : SysuiTestCase() {
+
+ @Test
+ fun platformTile() {
+ val spec = "spec"
+
+ val tileSpec = TileSpec.create(spec)
+
+ assertThat(tileSpec is TileSpec.PlatformTileSpec).isTrue()
+ assertThat(tileSpec.spec).isEqualTo(spec)
+ }
+
+ @Test
+ fun customTile() {
+ val componentName = ComponentName("test_pkg", "test_cls")
+ val spec = CUSTOM_TILE_PREFIX + componentName.flattenToString() + ")"
+
+ val tileSpec = TileSpec.create(spec)
+
+ assertThat(tileSpec is TileSpec.CustomTileSpec).isTrue()
+ assertThat(tileSpec.spec).isEqualTo(spec)
+ assertThat((tileSpec as TileSpec.CustomTileSpec).componentName).isEqualTo(componentName)
+ }
+
+ @Test
+ fun emptyCustomTile_invalid() {
+ val spec = CUSTOM_TILE_PREFIX + ")"
+
+ val tileSpec = TileSpec.create(spec)
+
+ assertThat(tileSpec).isEqualTo(TileSpec.Invalid)
+ }
+
+ @Test
+ fun invalidCustomTileSpec_invalid() {
+ val spec = CUSTOM_TILE_PREFIX + "invalid)"
+
+ val tileSpec = TileSpec.create(spec)
+
+ assertThat(tileSpec).isEqualTo(TileSpec.Invalid)
+ }
+
+ @Test
+ fun customTileNotEndsWithParenthesis_invalid() {
+ val componentName = ComponentName("test_pkg", "test_cls")
+ val spec = CUSTOM_TILE_PREFIX + componentName.flattenToString()
+
+ val tileSpec = TileSpec.create(spec)
+
+ assertThat(tileSpec).isEqualTo(TileSpec.Invalid)
+ }
+
+ @Test
+ fun emptySpec_invalid() {
+ assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
+ }
+
+ companion object {
+ private const val CUSTOM_TILE_PREFIX = "custom("
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 71ba21538a8e..aa98f08e9015 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -167,6 +167,7 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
captor.value.onUserSwitching(newID, userSwitchingReply)
verify(userSwitchingReply).sendResult(any())
@@ -290,6 +291,7 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
captor.value.onUserSwitching(newID, userSwitchingReply)
verify(userSwitchingReply).sendResult(any())
@@ -308,6 +310,7 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID)
captor.value.onUserSwitchComplete(newID)
assertThat(callback.calledOnUserChanged).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 9b6d29310d0b..b5e77e0fb693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -16,14 +16,18 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
+
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
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.verifyNoMoreInteractions;
@@ -31,6 +35,7 @@ import static org.mockito.Mockito.when;
import static java.util.Objects.requireNonNull;
+import android.database.ContentObserver;
import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
@@ -295,6 +300,42 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testEntryCancellationWillRebindViews() {
+ // Configure NotifUiAdjustmentProvider to set up SHOW_NOTIFICATION_SNOOZE value
+ mEntry = spy(mEntry);
+ mAdjustmentProvider.addDirtyListener(mock(Runnable.class));
+ when(mSecureSettings.getIntForUser(eq(SHOW_NOTIFICATION_SNOOZE), anyInt(), anyInt()))
+ .thenReturn(1);
+ ArgumentCaptor<ContentObserver> contentObserverCaptor = ArgumentCaptor.forClass(
+ ContentObserver.class);
+ verify(mSecureSettings).registerContentObserverForUser(eq(SHOW_NOTIFICATION_SNOOZE),
+ contentObserverCaptor.capture(), anyInt());
+ ContentObserver contentObserver = contentObserverCaptor.getValue();
+ contentObserver.onChange(false);
+
+ // GIVEN an inflated notification
+ mCollectionListener.onEntryInit(mEntry);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
+ verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
+ mNotifInflater.invokeInflateCallbackForEntry(mEntry);
+
+ // Verify that snooze is initially enabled: from Settings & notification is not cancelled
+ assertTrue(mAdjustmentProvider.calculateAdjustment(mEntry).isSnoozeEnabled());
+
+ // WHEN notification is cancelled, rebind views because snooze enabled value changes
+ when(mEntry.isCanceled()).thenReturn(true);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
+
+ assertFalse(mAdjustmentProvider.calculateAdjustment(mEntry).isSnoozeEnabled());
+
+ // THEN we rebind it
+ verify(mNotifInflater).rebindViews(eq(mEntry), any(), any());
+
+ // THEN we do not filter it because it's not the first inflation.
+ assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
public void testDoesntFilterInflatedNotifs() {
// GIVEN an inflated notification
mCollectionListener.onEntryInit(mEntry);
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 cc3b4ab0fb4e..28bdca97552d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -57,6 +57,7 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -100,6 +101,7 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeWindowLogger;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -123,7 +125,6 @@ import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleBadgeIconFactory;
@@ -135,6 +136,7 @@ import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.bubbles.StackEducationViewKt;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -145,6 +147,7 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import org.junit.After;
import org.junit.Before;
@@ -1669,6 +1672,60 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
+ public void testShowStackEdu_isNotConversationBubble() {
+ // Setup
+ setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */);
+ mBubbleController.updateBubble(bubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Click on bubble
+ Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey());
+ assertFalse(bubble.isConversation());
+ bubble.getIconView().callOnClick();
+
+ // Check education is not shown
+ BubbleStackView stackView = mBubbleController.getStackView();
+ assertFalse(stackView.isStackEduVisible());
+ }
+
+ @Test
+ public void testShowStackEdu_isConversationBubble() {
+ // Setup
+ setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
+ mBubbleController.updateBubble(bubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Click on bubble
+ Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey());
+ assertTrue(bubble.isConversation());
+ bubble.getIconView().callOnClick();
+
+ // Check education is shown
+ BubbleStackView stackView = mBubbleController.getStackView();
+ assertTrue(stackView.isStackEduVisible());
+ }
+
+ @Test
+ public void testShowStackEdu_isSeenConversationBubble() {
+ // Setup
+ setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true);
+ BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
+ mBubbleController.updateBubble(bubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Click on bubble
+ Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey());
+ assertTrue(bubble.isConversation());
+ bubble.getIconView().callOnClick();
+
+ // Check education is not shown
+ BubbleStackView stackView = mBubbleController.getStackView();
+ assertFalse(stackView.isStackEduVisible());
+ }
+
+ @Test
public void testShowOrHideAppBubble_addsAndExpand() {
assertThat(mBubbleController.isStackExpanded()).isFalse();
assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
@@ -1816,6 +1873,20 @@ public class BubblesTest extends SysuiTestCase {
mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor());
}
+ private BubbleEntry createBubbleEntry(boolean isConversation) {
+ NotificationEntry notificationEntry = mNotificationTestHelper.createBubble(mDeleteIntent);
+ if (isConversation) {
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("shortcutId")
+ .build();
+ NotificationEntryHelper.modifyRanking(notificationEntry)
+ .setIsConversation(true)
+ .setShortcutInfo(shortcutInfo)
+ .build();
+ }
+ return mBubblesManager.notifToBubbleEntry(notificationEntry);
+ }
+
/** Creates a context that will return a PackageManager with specific AppInfo. */
private Context setUpContextWithPackageManager(String pkg, ApplicationInfo info)
throws Exception {
@@ -1852,6 +1923,15 @@ public class BubblesTest extends SysuiTestCase {
bubbleMetadata.setFlags(flags);
}
+ /**
+ * Set preferences boolean value for key
+ * Used to setup global state for stack view education tests
+ */
+ private void setPrefBoolean(String key, boolean enabled) {
+ mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE)
+ .edit().putBoolean(key, enabled).apply();
+ }
+
private Notification.BubbleMetadata getMetadata() {
Intent target = new Intent(mContext, BubblesTestActivity.class);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 317928516c03..c3bb7716d9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -25,7 +25,6 @@ import android.view.WindowManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -42,6 +41,7 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewTransitions;
import java.util.Optional;
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 990dd64434a0..484e9566b036 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -338,9 +338,7 @@ class InputController {
}
synchronized (mLock) {
- InputDeviceDescriptor[] values = mInputDeviceDescriptors.values().toArray(
- new InputDeviceDescriptor[0]);
- for (InputDeviceDescriptor value : values) {
+ for (InputDeviceDescriptor value : mInputDeviceDescriptors.values()) {
if (value.mName.equals(deviceName)) {
throw new DeviceCreationException(
"Input device name already in use: " + deviceName);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 713b993824f9..8d8ed196be18 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -38,6 +38,8 @@ import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.StopUserOnSwitch;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
@@ -7500,9 +7502,9 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void registerUidFrozenStateChangedCallback(
@NonNull IUidFrozenStateChangedCallback callback) {
+ Preconditions.checkNotNull(callback, "callback cannot be null");
enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"registerUidFrozenStateChangedCallback()");
- Preconditions.checkNotNull(callback, "callback cannot be null");
synchronized (mUidFrozenStateChangedCallbackList) {
final boolean registered = mUidFrozenStateChangedCallbackList.register(callback);
if (!registered) {
@@ -7520,15 +7522,48 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void unregisterUidFrozenStateChangedCallback(
@NonNull IUidFrozenStateChangedCallback callback) {
+ Preconditions.checkNotNull(callback, "callback cannot be null");
enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
"unregisterUidFrozenStateChangedCallback()");
- Preconditions.checkNotNull(callback, "callback cannot be null");
synchronized (mUidFrozenStateChangedCallbackList) {
mUidFrozenStateChangedCallbackList.unregister(callback);
}
}
/**
+ * Query the frozen state of a list of UIDs.
+ *
+ * @param uids the array of UIDs which the client would like to know the frozen state of.
+ * @return An array containing the frozen state for each requested UID, by index. Will be set
+ * to {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
+ * if the UID is frozen. If the UID is not frozen or not found,
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN}
+ * will be set.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @Override
+ public @NonNull int[] getUidFrozenState(@NonNull int[] uids) {
+ Preconditions.checkNotNull(uids, "uid array cannot be null");
+ enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+ "getUidFrozenState()");
+
+ final int[] frozenStates = new int[uids.length];
+ synchronized (mProcLock) {
+ for (int i = 0; i < uids.length; i++) {
+ final UidRecord uidRec = mProcessList.mActiveUids.get(uids[i]);
+ if (uidRec != null && uidRec.areAllProcessesFrozen()) {
+ frozenStates[i] = UID_FROZEN_STATE_FROZEN;
+ } else {
+ frozenStates[i] = UID_FROZEN_STATE_UNFROZEN;
+ }
+ }
+ }
+ return frozenStates;
+ }
+
+ /**
* Notify the system that a UID has been frozen or unfrozen.
*
* @param uids The Uid(s) in question
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index a92723ee7036..749427730ca0 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.Nullable;
import android.content.IntentFilter;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -55,6 +56,16 @@ final class BroadcastFilter extends IntentFilter {
exported = _exported;
}
+ public @Nullable String getReceiverClassName() {
+ if (receiverId != null) {
+ final int index = receiverId.lastIndexOf('@');
+ if (index > 0) {
+ return receiverId.substring(0, index);
+ }
+ }
+ return null;
+ }
+
@NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0cdd4e9041e9..056e17a5ef3c 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -393,6 +393,10 @@ class BroadcastProcessQueue {
setProcessInstrumented(false);
setProcessPersistent(false);
}
+
+ // Since we may have just changed our PID, invalidate cached strings
+ mCachedToString = null;
+ mCachedToShortString = null;
}
/**
@@ -1128,16 +1132,16 @@ class BroadcastProcessQueue {
@Override
public String toString() {
if (mCachedToString == null) {
- mCachedToString = "BroadcastProcessQueue{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+ mCachedToString = "BroadcastProcessQueue{" + toShortString() + "}";
}
return mCachedToString;
}
public String toShortString() {
if (mCachedToShortString == null) {
- mCachedToShortString = processName + "/" + UserHandle.formatUid(uid);
+ mCachedToShortString = Integer.toHexString(System.identityHashCode(this))
+ + " " + ((app != null) ? app.getPid() : "?") + ":" + processName + "/"
+ + UserHandle.formatUid(uid);
}
return mCachedToShortString;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 1f0b1628aa22..93173abeb106 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -32,6 +32,7 @@ import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.getReceiverClassName;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
@@ -1040,7 +1041,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
if (app != null && !app.isDebugging()) {
- mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
+ final String packageName = getReceiverPackageName(receiver);
+ final String className = getReceiverClassName(receiver);
+ mService.appNotResponding(queue.app,
+ TimeoutRecord.forBroadcastReceiver(r.intent, packageName, className));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index e6ef3b468d2d..c368290386a0 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -832,6 +832,14 @@ final class BroadcastRecord extends Binder {
}
}
+ static @Nullable String getReceiverClassName(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).getReceiverClassName();
+ } else /* if (receiver instanceof ResolveInfo) */ {
+ return ((ResolveInfo) receiver).activityInfo.name;
+ }
+ }
+
static int getReceiverPriority(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).getPriority();
@@ -1068,9 +1076,7 @@ final class BroadcastRecord extends Binder {
if (label == null) {
label = intent.toString();
}
- mCachedToString = "BroadcastRecord{"
- + Integer.toHexString(System.identityHashCode(this))
- + " u" + userId + " " + label + "}";
+ mCachedToString = "BroadcastRecord{" + toShortString() + "}";
}
return mCachedToString;
}
@@ -1082,7 +1088,7 @@ final class BroadcastRecord extends Binder {
label = intent.toString();
}
mCachedToShortString = Integer.toHexString(System.identityHashCode(this))
- + ":" + label + "/u" + userId;
+ + " " + label + "/u" + userId;
}
return mCachedToShortString;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7a92434a4ba4..b9c9efee0ffd 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -24,6 +24,7 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -2273,6 +2274,15 @@ public class OomAdjuster {
capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
}
}
+ if ((cstate.getCurCapability()
+ & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
+ if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // This is used to grant network access to User Initiated Jobs.
+ if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+ capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+ }
+ }
+ }
if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
continue;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4e401b258550..b26a1705b309 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4900,12 +4900,14 @@ public final class ProcessList {
final boolean isAllowed =
isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState(),
uidRec.getCurCapability())
- || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState());
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState(),
+ uidRec.getCurCapability());
// Denotes whether uid's process state was previously allowed network access.
final boolean wasAllowed =
isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState(),
uidRec.getSetCapability())
- || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState());
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState(),
+ uidRec.getSetCapability());
// When the uid is coming to foreground, AMS should inform the app thread that it should
// block for the network rules to get updated before launching an activity.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 1b378837e558..d926c2c7c7a8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2141,6 +2141,17 @@ class UserController implements Handler.Callback {
final int observerCount = mUserSwitchObservers.beginBroadcast();
if (observerCount > 0) {
+ for (int i = 0; i < observerCount; i++) {
+ final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
+ t.traceBegin("onBeforeUserSwitching-" + name);
+ try {
+ mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId);
+ } catch (RemoteException e) {
+ // Ignore
+ } finally {
+ t.traceEnd();
+ }
+ }
final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
synchronized (mLock) {
uss.switching = true;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5893f1efc505..3780620ca0d3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11602,6 +11602,7 @@ public class AudioService extends IAudioService.Stub
return false;
}
+ final long token = Binder.clearCallingIdentity();
try {
if (!projectionService.isCurrentProjection(projection)) {
Log.w(TAG, "App passed invalid MediaProjection token");
@@ -11611,6 +11612,8 @@ public class AudioService extends IAudioService.Stub
Log.e(TAG, "Can't call .isCurrentProjection() on IMediaProjectionManager"
+ projectionService.asBinder(), e);
return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 01ffc7e29ac0..128ef0b2a802 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -82,7 +82,9 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -97,7 +99,9 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -1138,12 +1142,28 @@ public class FingerprintService extends SystemService {
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
+ final CountDownLatch latch = new CountDownLatch(mRegistry.getProviders().size());
for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
- provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
- true /* favorHalEnrollments */);
+ provider.scheduleInternalCleanup(props.sensorId, userId,
+ new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(
+ @NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ latch.countDown();
+ if (!success) {
+ Slog.e(TAG, "Sync virtual enrollments failed");
+ }
+ }
+ }, true /* favorHalEnrollments */);
}
}
+ try {
+ latch.await(3, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to wait for sync finishing", e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 745e40411bad..c3a4c2e389ac 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1419,6 +1419,7 @@ public final class DisplayManagerService extends SystemService {
}
if (projection != null) {
+ final long firstToken = Binder.clearCallingIdentity();
try {
if (!getProjectionService().isCurrentProjection(projection)) {
throw new SecurityException("Cannot create VirtualDisplay with "
@@ -1427,6 +1428,8 @@ public final class DisplayManagerService extends SystemService {
flags = projection.applyVirtualDisplayFlags(flags);
} catch (RemoteException e) {
throw new SecurityException("unable to validate media projection or flags");
+ } finally {
+ Binder.restoreCallingIdentity(firstToken);
}
}
@@ -1494,7 +1497,7 @@ public final class DisplayManagerService extends SystemService {
throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
}
- final long token = Binder.clearCallingIdentity();
+ final long secondToken = Binder.clearCallingIdentity();
try {
final int displayId;
synchronized (mSyncRoot) {
@@ -1566,7 +1569,7 @@ public final class DisplayManagerService extends SystemService {
return displayId;
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(secondToken);
}
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index e8327018e144..e3d38e7a25e9 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.app.BroadcastOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -421,6 +422,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
// Runs on the handler.
private void handleSendStatusChangeBroadcast() {
final Intent intent;
+ final BroadcastOptions options;
synchronized (getSyncRoot()) {
if (!mPendingStatusChangeBroadcast) {
return;
@@ -431,10 +433,13 @@ final class WifiDisplayAdapter extends DisplayAdapter {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
getWifiDisplayStatusLocked());
+
+ options = BroadcastOptions.makeBasic();
+ options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
}
// Send protected broadcast about wifi display status to registered receivers.
- getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(intent, UserHandle.ALL, null, options.toBundle());
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c0deb3f8274b..805ff6611c29 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3774,11 +3774,12 @@ public class HdmiControlService extends SystemService {
}
try {
record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
+ return true;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify vendor command reception", e);
}
}
- return true;
+ 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 7a0bf0cacdfb..b440208e3e32 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -20,6 +20,8 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.provider.Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE;
+import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
@@ -2067,10 +2069,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
synchronized (ImfLock.class) {
+ if (!isStylusHandwritingEnabled(mContext, userId)) {
+ return false;
+ }
+
+ // Check if selected IME of current user supports handwriting.
if (userId == mSettings.getCurrentUserId()) {
return mBindingController.supportsStylusHandwriting();
}
-
//TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
//TODO(b/210039666): use cache.
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
@@ -2081,6 +2087,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ private boolean isStylusHandwritingEnabled(
+ @NonNull Context context, @UserIdInt int userId) {
+ // If user is a profile, use preference of it`s parent profile.
+ final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId);
+ if (Settings.Secure.getIntForUser(context.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, STYLUS_HANDWRITING_DEFAULT_VALUE,
+ profileParentUserId) == 0) {
+ return false;
+ }
+ return true;
+ }
+
@GuardedBy("ImfLock.class")
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
@@ -3418,8 +3436,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void prepareStylusHandwritingDelegation(
@NonNull IInputMethodClient client,
+ @UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName) {
+ if (!isStylusHandwritingEnabled(mContext, userId)) {
+ Slog.w(TAG, "Can not prepare stylus handwriting delegation. Stylus handwriting"
+ + " pref is disabled for user: " + userId);
+ return;
+ }
if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
throw new IllegalArgumentException("Delegator doesn't match Uid");
@@ -3430,8 +3454,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public boolean acceptStylusHandwritingDelegation(
@NonNull IInputMethodClient client,
+ @UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName) {
+ if (!isStylusHandwritingEnabled(mContext, userId)) {
+ Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting"
+ + " pref is disabled for user: " + userId);
+ return false;
+ }
if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) {
return false;
}
diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
index 2be2ef8c35af..7a70db22106e 100644
--- a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
+++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
@@ -20,24 +20,32 @@ import static android.os.Process.INVALID_UID;
import com.android.internal.util.FrameworkStatsLog;
+import java.util.Locale;
+
/**
* Holds data used to report the ApplicationLocalesChanged atom.
*/
public final class AppLocaleChangedAtomRecord {
+ private static final String DEFAULT_PREFIX = "default-";
final int mCallingUid;
int mTargetUid = INVALID_UID;
- String mNewLocales = "";
- String mPrevLocales = "";
+ String mNewLocales = DEFAULT_PREFIX;
+ String mPrevLocales = DEFAULT_PREFIX;
int mStatus = FrameworkStatsLog
.APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED;
int mCaller = FrameworkStatsLog
.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_UNKNOWN;
AppLocaleChangedAtomRecord(int callingUid) {
this.mCallingUid = callingUid;
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultLocale != null) {
+ this.mNewLocales = DEFAULT_PREFIX + defaultLocale.toLanguageTag();
+ this.mPrevLocales = DEFAULT_PREFIX + defaultLocale.toLanguageTag();
+ }
}
void setNewLocales(String newLocales) {
- this.mNewLocales = newLocales;
+ this.mNewLocales = convertEmptyLocales(newLocales);
}
void setTargetUid(int targetUid) {
@@ -45,7 +53,7 @@ public final class AppLocaleChangedAtomRecord {
}
void setPrevLocales(String prevLocales) {
- this.mPrevLocales = prevLocales;
+ this.mPrevLocales = convertEmptyLocales(prevLocales);
}
void setStatus(int status) {
@@ -55,4 +63,16 @@ public final class AppLocaleChangedAtomRecord {
void setCaller(int caller) {
this.mCaller = caller;
}
+
+ private String convertEmptyLocales(String locales) {
+ String target = locales;
+ if ("".equals(locales)) {
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultLocale != null) {
+ target = DEFAULT_PREFIX + defaultLocale.toLanguageTag();
+ }
+ }
+
+ return target;
+ }
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 6cd2ed41e94c..0049213cbf55 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -44,6 +44,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -377,7 +378,8 @@ class LocaleManagerBackupHelper {
// Restore the locale immediately
try {
mLocaleManagerService.setApplicationLocales(pkgName, userId,
- LocaleList.forLanguageTags(localesInfo.mLocales), localesInfo.mSetFromDelegate);
+ LocaleList.forLanguageTags(localesInfo.mLocales), localesInfo.mSetFromDelegate,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
if (DEBUG) {
Slog.d(TAG, "Restored locales=" + localesInfo.mLocales + " fromDelegate="
+ localesInfo.mSetFromDelegate + " for package=" + pkgName);
@@ -662,7 +664,9 @@ class LocaleManagerBackupHelper {
try {
LocaleConfig localeConfig = new LocaleConfig(
mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)));
- mLocaleManagerService.removeUnsupportedAppLocales(packageName, userId, localeConfig);
+ mLocaleManagerService.removeUnsupportedAppLocales(packageName, userId, localeConfig,
+ FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APP_UPDATE_LOCALES_CHANGE);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Can not found the package name : " + packageName + " / " + e);
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index e3a555bd2f6a..43e346a5bfa3 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -182,8 +182,11 @@ public class LocaleManagerService extends SystemService {
@Override
public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
@NonNull LocaleList locales, boolean fromDelegate) throws RemoteException {
+ int caller = fromDelegate
+ ? FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DELEGATE
+ : FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS;
LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales,
- fromDelegate);
+ fromDelegate, caller);
}
@Override
@@ -226,13 +229,14 @@ public class LocaleManagerService extends SystemService {
* Sets the current UI locales for a specified app.
*/
public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
- @NonNull LocaleList locales, boolean fromDelegate)
+ @NonNull LocaleList locales, boolean fromDelegate, int caller)
throws RemoteException, IllegalArgumentException {
AppLocaleChangedAtomRecord atomRecordForMetrics = new
AppLocaleChangedAtomRecord(Binder.getCallingUid());
try {
requireNonNull(appPackageName);
requireNonNull(locales);
+ atomRecordForMetrics.setCaller(caller);
atomRecordForMetrics.setNewLocales(locales.toLanguageTags());
//Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
userId = mActivityManagerInternal.handleIncomingUser(
@@ -273,8 +277,8 @@ public class LocaleManagerService extends SystemService {
+ " and user " + userId);
}
- atomRecordForMetrics.setPrevLocales(getApplicationLocalesUnchecked(appPackageName, userId)
- .toLanguageTags());
+ atomRecordForMetrics.setPrevLocales(
+ getApplicationLocalesUnchecked(appPackageName, userId).toLanguageTags());
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
@@ -619,7 +623,10 @@ public class LocaleManagerService extends SystemService {
Slog.d(TAG, "remove the override LocaleConfig");
file.delete();
}
- removeUnsupportedAppLocales(appPackageName, userId, resLocaleConfig);
+ removeUnsupportedAppLocales(appPackageName, userId, resLocaleConfig,
+ FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DYNAMIC_LOCALES_CHANGE
+ );
atomRecord.setOverrideRemoved(true);
atomRecord.setStatus(FrameworkStatsLog
.APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS);
@@ -661,7 +668,10 @@ public class LocaleManagerService extends SystemService {
}
atomicFile.finishWrite(stream);
// Clear per-app locales if they are not in the override LocaleConfig.
- removeUnsupportedAppLocales(appPackageName, userId, overrideLocaleConfig);
+ removeUnsupportedAppLocales(appPackageName, userId, overrideLocaleConfig,
+ FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DYNAMIC_LOCALES_CHANGE
+ );
if (overrideLocaleConfig.isSameLocaleConfig(resLocaleConfig)) {
Slog.d(TAG, "setOverrideLocaleConfig, same as the app's LocaleConfig");
atomRecord.setSameAsResConfig(true);
@@ -678,9 +688,12 @@ public class LocaleManagerService extends SystemService {
/**
* Checks if the per-app locales are in the LocaleConfig. Per-app locales missing from the
* LocaleConfig will be removed.
+ *
+ * <p><b>Note:</b> Check whether to remove the per-app locales when the app is upgraded or
+ * the LocaleConfig is overridden.
*/
void removeUnsupportedAppLocales(String appPackageName, int userId,
- LocaleConfig localeConfig) {
+ LocaleConfig localeConfig, int caller) {
LocaleList appLocales = getApplicationLocalesUnchecked(appPackageName, userId);
// Remove the per-app locales from the locale list if they don't exist in the LocaleConfig.
boolean resetAppLocales = false;
@@ -707,7 +720,7 @@ public class LocaleManagerService extends SystemService {
try {
setApplicationLocales(appPackageName, userId,
new LocaleList(newAppLocales.toArray(locales)),
- mBackupHelper.areLocalesSetFromDelegate(userId, appPackageName));
+ mBackupHelper.areLocalesSetFromDelegate(userId, appPackageName), caller);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Could not set locales for " + appPackageName, e);
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index b75b7d4daacd..48acc7c2ba8d 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.media.projection;
+import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
@@ -282,7 +283,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public IMediaProjection createProjection(int uid, String packageName, int type,
boolean isPermanentGrant) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
+ "projection permission");
@@ -314,16 +315,21 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public boolean isCurrentProjection(IMediaProjection projection) {
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to check "
+ + "if the given projection is current.");
+ }
return MediaProjectionManagerService.this.isCurrentProjection(
projection == null ? null : projection.asBinder());
}
@Override // Binder call
public MediaProjectionInfo getActiveProjectionInfo() {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
- + "projection callbacks");
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to get "
+ + "active projection info");
}
final long token = Binder.clearCallingIdentity();
try {
@@ -335,10 +341,10 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public void stopActiveProjection() {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
- + "projection callbacks");
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to stop "
+ + "the active projection");
}
final long token = Binder.clearCallingIdentity();
try {
@@ -352,7 +358,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public void notifyActiveProjectionCapturedContentResized(int width, int height) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+ "on captured content resize");
@@ -372,10 +378,10 @@ public final class MediaProjectionManagerService extends SystemService
@Override
public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
- + "on captured content resize");
+ + "on captured content visibility changed");
}
if (!isCurrentProjection(mProjectionGrant)) {
return;
@@ -392,7 +398,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override //Binder call
public void addCallback(final IMediaProjectionWatcherCallback callback) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
+ "projection callbacks");
@@ -407,7 +413,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override
public void removeCallback(IMediaProjectionWatcherCallback callback) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "
+ "projection callbacks");
@@ -512,6 +518,11 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public int applyVirtualDisplayFlags(int flags) {
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to apply virtual "
+ + "display flags.");
+ }
if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
@@ -660,11 +671,21 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public void setLaunchCookie(IBinder launchCookie) {
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to set launch "
+ + "cookie.");
+ }
mLaunchCookie = launchCookie;
}
@Override // Binder call
public IBinder getLaunchCookie() {
+ if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to get launch "
+ + "cookie.");
+ }
return mLaunchCookie;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 92be0943c9f4..4c36b910e77b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -5550,7 +5550,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// Do this without the lock held. handleUidChanged() and handleUidGone() are
// called from the handler, so there's no multi-threading issue.
if (updated) {
- updateNetworkStats(uid, isProcStateAllowedWhileOnRestrictBackground(procState));
+ updateNetworkStats(uid,
+ isProcStateAllowedWhileOnRestrictBackground(procState, capability));
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index a561390ac7e4..d3dea0d96812 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -182,7 +182,7 @@ public class BubbleExtractor implements NotificationSignalExtractor {
/**
* Whether an intent is properly configured to display in an {@link
- * com.android.wm.shell.TaskView} for bubbling.
+ * TaskView} for bubbling.
*
* @param context the context to use.
* @param pendingIntent the pending intent of the bubble.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a4eb417be4e1..65dcec702ef4 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3323,7 +3323,8 @@ public class NotificationManagerService extends SystemService {
final boolean isSystemToast = isCallerSystemOrPhone()
|| PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
boolean isAppRenderedToast = (callback != null);
- if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) {
+ if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
+ isSystemToast)) {
return;
}
@@ -3393,7 +3394,7 @@ public class NotificationManagerService extends SystemService {
}
}
- private boolean checkCanEnqueueToast(String pkg, int callingUid,
+ private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId,
boolean isAppRenderedToast, boolean isSystemToast) {
final boolean isPackageSuspended = isPackagePaused(pkg);
final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
@@ -3423,6 +3424,13 @@ public class NotificationManagerService extends SystemService {
return false;
}
+ int userId = UserHandle.getUserId(callingUid);
+ if (!isSystemToast && !mUmInternal.isUserVisible(userId, displayId)) {
+ Slog.e(TAG, "Suppressing toast from package " + pkg + "/" + callingUid + " as user "
+ + userId + " is not visible on display " + displayId);
+ return false;
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index acd4a96c2817..6f7ce80e42b1 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -571,6 +571,7 @@ public class ComputerEngine implements Computer {
if (!blockInstantResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
+ ri.userHandle = UserHandle.of(userId);
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ee2e4589e1aa..54f87d004b5c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -590,7 +590,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private int mDoubleTapOnHomeBehavior;
// Whether to lock the device after the next app transition has finished.
- private boolean mLockAfterAppTransitionFinished;
+ boolean mLockAfterAppTransitionFinished;
// Allowed theater mode wake actions
private boolean mAllowTheaterModeWakeFromKey;
diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
index f653e4b26438..6cb6dc07f8b8 100644
--- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
+++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
@@ -28,7 +28,8 @@ public final class ProcfsMemoryUtil {
"VmHWM:",
"VmRSS:",
"RssAnon:",
- "VmSwap:"
+ "RssShmem:",
+ "VmSwap:",
};
private static final String[] VMSTAT_KEYS = new String[] {
"oom_kill"
@@ -38,7 +39,7 @@ public final class ProcfsMemoryUtil {
/**
* Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
- * VmSwap fields in /proc/pid/status in kilobytes or null if not available.
+ * VmSwap, RssShmem fields in /proc/pid/status in kilobytes or null if not available.
*/
@Nullable
public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
@@ -46,8 +47,9 @@ public final class ProcfsMemoryUtil {
output[0] = -1;
output[3] = -1;
output[4] = -1;
+ output[5] = -1;
Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
- if (output[0] == -1 || output[3] == -1 || output[4] == -1) {
+ if (output[0] == -1 || output[3] == -1 || output[4] == -1 || output[5] == -1) {
// Could not open or parse file.
return null;
}
@@ -56,7 +58,8 @@ public final class ProcfsMemoryUtil {
snapshot.rssHighWaterMarkInKilobytes = (int) output[1];
snapshot.rssInKilobytes = (int) output[2];
snapshot.anonRssInKilobytes = (int) output[3];
- snapshot.swapInKilobytes = (int) output[4];
+ snapshot.rssShmemKilobytes = (int) output[4];
+ snapshot.swapInKilobytes = (int) output[5];
return snapshot;
}
@@ -101,6 +104,7 @@ public final class ProcfsMemoryUtil {
public int rssInKilobytes;
public int anonRssInKilobytes;
public int swapInKilobytes;
+ public int rssShmemKilobytes;
}
/** Reads and parses selected entries of /proc/vmstat. */
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index f8a4b04180c3..b2f48d9e3d8c 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2290,7 +2290,8 @@ public class StatsPullAtomService extends SystemService {
managedProcess.processName, managedProcess.pid, managedProcess.oomScore,
snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes,
snapshot.anonRssInKilobytes + snapshot.swapInKilobytes,
- gpuMemPerPid.get(managedProcess.pid), managedProcess.hasForegroundServices));
+ gpuMemPerPid.get(managedProcess.pid), managedProcess.hasForegroundServices,
+ snapshot.rssShmemKilobytes));
}
// Complement the data with native system processes. Given these measurements can be taken
// in response to LMKs happening, we want to first collect the managed app stats (to
@@ -2309,7 +2310,8 @@ public class StatsPullAtomService extends SystemService {
-1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/,
snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes,
snapshot.anonRssInKilobytes + snapshot.swapInKilobytes,
- gpuMemPerPid.get(pid), false /* has_foreground_services */));
+ gpuMemPerPid.get(pid), false /* has_foreground_services */,
+ snapshot.rssShmemKilobytes));
}
return StatsManager.PULL_SUCCESS;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index f14a432f73ae..ff1c28ad1973 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -778,13 +778,12 @@ class ActivityClientController extends IActivityClientController.Stub {
&& r.mTransitionController.inPlayingTransition(r)
&& !r.mTransitionController.isCollecting()
? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
- if (transition != null) {
- r.mTransitionController.requestStartTransition(transition, null /*startTask */,
- null /* remoteTransition */, null /* displayChange */);
- }
final boolean changed = r != null && r.setOccludesParent(true);
if (transition != null) {
if (changed) {
+ r.mTransitionController.requestStartTransition(transition,
+ null /*startTask */, null /* remoteTransition */,
+ null /* displayChange */);
r.mTransitionController.setReady(r.getDisplayContent());
} else {
transition.abort();
@@ -818,13 +817,12 @@ class ActivityClientController extends IActivityClientController.Stub {
final Transition transition = r.mTransitionController.inPlayingTransition(r)
&& !r.mTransitionController.isCollecting()
? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
- if (transition != null) {
- r.mTransitionController.requestStartTransition(transition, null /*startTask */,
- null /* remoteTransition */, null /* displayChange */);
- }
final boolean changed = r.setOccludesParent(false);
if (transition != null) {
if (changed) {
+ r.mTransitionController.requestStartTransition(transition,
+ null /*startTask */, null /* remoteTransition */,
+ null /* displayChange */);
r.mTransitionController.setReady(r.getDisplayContent());
} else {
transition.abort();
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 95fd82ff1154..a757d90b75ba 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -398,6 +398,7 @@ class ActivityMetricsLogger {
/** Returns {@code true} if the incoming activity can belong to this transition. */
boolean canCoalesce(ActivityRecord r) {
return mLastLaunchedActivity.mDisplayContent == r.mDisplayContent
+ && mLastLaunchedActivity.getTask().getBounds().equals(r.getTask().getBounds())
&& mLastLaunchedActivity.getWindowingMode() == r.getWindowingMode();
}
@@ -646,7 +647,7 @@ class ActivityMetricsLogger {
void notifyActivityLaunched(@NonNull LaunchingState launchingState, int resultCode,
boolean newActivityCreated, @Nullable ActivityRecord launchedActivity,
@Nullable ActivityOptions options) {
- if (launchedActivity == null) {
+ if (launchedActivity == null || launchedActivity.getTask() == null) {
// The launch is aborted, e.g. intent not resolved, class not found.
abort(launchingState, "nothing launched");
return;
@@ -1154,6 +1155,8 @@ class ActivityMetricsLogger {
sb.setLength(0);
sb.append("Displayed ");
sb.append(info.launchedActivityShortComponentName);
+ sb.append(" for user ");
+ sb.append(info.userId);
sb.append(": ");
TimeUtils.formatDuration(info.windowsDrawnDelayMs, sb);
Log.i(TAG, sb.toString());
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2ea59b3fd6fd..81dabfd48bf3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -179,6 +179,7 @@ import static com.android.server.wm.ActivityRecordProto.PROC_ID;
import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_SEND_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
import static com.android.server.wm.ActivityRecordProto.STARTING_MOVED;
import static com.android.server.wm.ActivityRecordProto.STARTING_WINDOW;
@@ -5266,10 +5267,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTransitionController.collect(this);
} else {
inFinishingTransition = mTransitionController.inFinishingTransition(this);
- if (!inFinishingTransition) {
+ if (!inFinishingTransition && !mDisplayContent.isSleeping()) {
Slog.e(TAG, "setVisibility=" + visible
+ " while transition is not collecting or finishing "
+ this + " caller=" + Debug.getCallers(8));
+ // Force showing the parents because they may be hidden by previous transition.
+ if (visible) {
+ final Transaction t = getSyncTransaction();
+ for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent;
+ p = p.getParent()) {
+ if (p.mSurfaceControl != null) {
+ t.show(p.mSurfaceControl);
+ }
+ }
+ }
}
}
}
@@ -10214,6 +10225,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
proto.write(ENABLE_RECENTS_SCREENSHOT, mEnableRecentsScreenshot);
proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
+ proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index f8fb76acf81e..7c1e9071b926 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -574,7 +574,9 @@ public class ActivityStartController {
mService.deferWindowLayout();
try {
final TransitionController controller = r.mTransitionController;
- if (controller.getTransitionPlayer() != null) {
+ final Transition transition = controller.getCollectingTransition();
+ if (transition != null) {
+ transition.setRemoteAnimationApp(r.app.getThread());
controller.collect(task);
controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7b9cc6fee602..bbdaa24a694c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2011,6 +2011,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return;
}
+ if (r == mRootWindowContainer.getTopResumedActivity()) {
+ setLastResumedActivityUncheckLocked(r, "setFocusedTask-alreadyTop");
+ return;
+ }
final Transition transition = (getTransitionController().isCollecting()
|| !getTransitionController().isShellTransitionsEnabled()) ? null
: getTransitionController().createTransition(TRANSIT_TO_FRONT);
@@ -4788,11 +4792,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// until we've committed to the gesture. The focus will be transferred at the end of
// the transition (if the transient launch is committed) or early if explicitly requested
// via `setFocused*`.
+ boolean focusedAppChanged = false;
if (!getTransitionController().isTransientCollect(r)) {
- final Task prevFocusTask = r.mDisplayContent.mFocusedApp != null
- ? r.mDisplayContent.mFocusedApp.getTask() : null;
- final boolean changed = r.mDisplayContent.setFocusedApp(r);
- if (changed) {
+ focusedAppChanged = r.mDisplayContent.setFocusedApp(r);
+ if (focusedAppChanged) {
mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
true /*updateInputWindows*/);
}
@@ -4801,13 +4804,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mTaskSupervisor.mRecentTasks.add(task);
}
- applyUpdateLockStateLocked(r);
- applyUpdateVrModeLocked(r);
+ if (focusedAppChanged) {
+ applyUpdateLockStateLocked(r);
+ }
+ if (mVrController.mVrService != null) {
+ applyUpdateVrModeLocked(r);
+ }
- EventLogTags.writeWmSetResumedActivity(
- r == null ? -1 : r.mUserId,
- r == null ? "NULL" : r.shortComponentName,
- reason);
+ EventLogTags.writeWmSetResumedActivity(r.mUserId, r.shortComponentName, reason);
}
final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 7460b5840fca..597c8bf45132 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -929,7 +929,7 @@ public class AppTransitionController {
/**
* Returns {@code true} if a given {@link WindowContainer} is an embedded Task in
- * {@link com.android.wm.shell.TaskView}.
+ * {@link TaskView}.
*
* Note that this is a short term workaround to support Android Auto until it migrate to
* ShellTransition. This should only be used by {@link #getAnimationTargets}.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1604c2a0343b..a44f25ca8051 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3300,7 +3300,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
// -> this DisplayContent.
setRemoteInsetsController(null);
- mWmService.mAnimator.removeDisplayLocked(mDisplayId);
mOverlayLayer.release();
mA11yOverlayLayer.release();
mWindowingLayer.release();
@@ -5312,8 +5311,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// {@link DisplayContent} ready for use.
mDisplayReady = true;
- mWmService.mAnimator.addDisplayLocked(mDisplayId);
-
if (mWmService.mDisplayManagerInternal != null) {
mWmService.mDisplayManagerInternal
.setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index c3c727a1d879..052c09a0e0eb 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -326,7 +326,7 @@ class EmbeddedWindowController {
@Override
public boolean shouldControlIme() {
- return false;
+ return mHostWindowState != null;
}
@Override
@@ -336,6 +336,9 @@ class EmbeddedWindowController {
@Override
public InsetsControlTarget getImeControlTarget() {
+ if (mHostWindowState != null) {
+ return mHostWindowState.getImeControlTarget();
+ }
return mWmService.getDefaultDisplayContentLocked().mRemoteInsetsControlTarget;
}
@@ -346,7 +349,7 @@ class EmbeddedWindowController {
@Override
public ActivityRecord getActivityRecord() {
- return null;
+ return mHostActivityRecord;
}
@Override
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index dda0d6c3c3f2..b38666522754 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,9 +976,10 @@ class RecentTasks {
if (!task.mUserSetupComplete) {
// Don't include task launched while user is not done setting-up.
- if (DEBUG_RECENTS) {
- Slog.d(TAG_RECENTS, "Skipping, user setup not complete: " + task);
- }
+
+ // NOTE: not guarding with DEBUG_RECENTS as it's not frequent enough to spam logcat,
+ // but is useful when running CTS.
+ Slog.d(TAG_RECENTS, "Skipping, user setup not complete: " + task);
continue;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2f5634362e68..07daa4b22ac9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2349,12 +2349,23 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
// Prepare transition before resume top activity, so it can be collected.
- if (!displayShouldSleep && display.isDefaultDisplay
- && !display.getDisplayPolicy().isAwake()
- && display.mTransitionController.isShellTransitionsEnabled()
+ if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled()
&& !display.mTransitionController.isCollecting()) {
- display.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
- 0 /* flags */, null /* trigger */, display);
+ int transit = TRANSIT_NONE;
+ if (!display.getDisplayPolicy().isAwake()) {
+ // Note that currently this only happens on default display because non-default
+ // display is always awake.
+ transit = TRANSIT_WAKE;
+ } else if (display.isKeyguardOccluded()) {
+ // The display was awake so this is resuming activity for occluding keyguard.
+ transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+ }
+ if (transit != TRANSIT_NONE) {
+ display.mTransitionController.requestStartTransition(
+ display.mTransitionController.createTransition(transit),
+ null /* startTask */, null /* remoteTransition */,
+ null /* displayChange */);
+ }
}
// Set the sleeping state of the root tasks on the display.
display.forAllRootTasks(rootTask -> {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5a4615ad9578..b7e2265e3a16 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5636,8 +5636,6 @@ class Task extends TaskFragment {
mWmService.mSyncEngine.queueSyncSet(
() -> mTransitionController.moveToCollecting(transition),
() -> {
- mTransitionController.requestStartTransition(transition, tr,
- null /* remoteTransition */, null /* displayChange */);
// Need to check again since this happens later and the system might
// be in a different state.
if (!canMoveTaskToBack(tr)) {
@@ -5646,6 +5644,8 @@ class Task extends TaskFragment {
transition.abort();
return;
}
+ mTransitionController.requestStartTransition(transition, tr,
+ null /* remoteTransition */, null /* displayChange */);
moveTaskToBackInner(tr);
});
} else {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bb2c8f13f217..683767e474ef 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -66,6 +66,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
@@ -83,7 +84,6 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.window.RemoteTransition;
import android.window.ScreenCapture;
import android.window.TransitionInfo;
@@ -160,7 +160,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
private final Token mToken;
- private RemoteTransition mRemoteTransition = null;
+ private IApplicationThread mRemoteAnimApp;
/** Only use for clean-up after binder death! */
private SurfaceControl.Transaction mStartTransaction = null;
@@ -1075,12 +1075,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return mForcePlaying;
}
- void setRemoteTransition(RemoteTransition remoteTransition) {
- mRemoteTransition = remoteTransition;
+ void setRemoteAnimationApp(IApplicationThread app) {
+ mRemoteAnimApp = app;
}
- RemoteTransition getRemoteTransition() {
- return mRemoteTransition;
+ /** Returns the app which will run the transition animation. */
+ IApplicationThread getRemoteAnimationApp() {
+ return mRemoteAnimApp;
}
void setNoAnimation(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 7e267e47ede3..bcb8c46de5ed 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -567,7 +567,9 @@ class TransitionController {
transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
transition.mLogger.mRequest = request;
mTransitionPlayer.requestStartTransition(transition.getToken(), request);
- transition.setRemoteTransition(remoteTransition);
+ if (remoteTransition != null) {
+ transition.setRemoteAnimationApp(remoteTransition.getAppThread());
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
transition.start();
@@ -779,9 +781,8 @@ class TransitionController {
mRemotePlayer.clear();
return;
}
- final RemoteTransition remote = transition.getRemoteTransition();
- if (remote == null) return;
- final IApplicationThread appThread = remote.getAppThread();
+ final IApplicationThread appThread = transition.getRemoteAnimationApp();
+ if (appThread == null || appThread == mTransitionPlayerProc.getThread()) return;
final WindowProcessController delegate = mAtm.getProcessController(appThread);
if (delegate == null) return;
mRemotePlayer.update(delegate, isPlaying, true /* predict */);
diff --git a/services/core/java/com/android/server/wm/VrController.java b/services/core/java/com/android/server/wm/VrController.java
index 9e159aba4d77..241a8ae88ae7 100644
--- a/services/core/java/com/android/server/wm/VrController.java
+++ b/services/core/java/com/android/server/wm/VrController.java
@@ -126,6 +126,9 @@ final class VrController {
}
};
+ /** If it is null after system ready, then VR mode is not supported. */
+ VrManagerInternal mVrService;
+
/**
* Create new VrController instance.
*
@@ -141,6 +144,7 @@ final class VrController {
public void onSystemReady() {
VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class);
if (vrManagerInternal != null) {
+ mVrService = vrManagerInternal;
vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener);
}
}
@@ -181,7 +185,7 @@ final class VrController {
public boolean onVrModeChanged(ActivityRecord record) {
// This message means that the top focused activity enabled VR mode (or an activity
// that previously set this has become focused).
- VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+ final VrManagerInternal vrService = mVrService;
if (vrService == null) {
// VR mode isn't supported on this device.
return false;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 10bedd4b921f..adc0595f305b 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -30,7 +30,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.content.Context;
import android.os.Trace;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.Choreographer;
import android.view.SurfaceControl;
@@ -66,7 +65,6 @@ public class WindowAnimator {
int mBulkUpdateParams = 0;
Object mLastWindowFreezeSource;
- SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
private boolean mInitialized = false;
private Choreographer mChoreographer;
@@ -98,8 +96,7 @@ public class WindowAnimator {
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mGlobalLock) {
mAnimationFrameCallbackScheduled = false;
- final long vsyncId = mChoreographer.getVsyncId();
- animate(frameTimeNs, vsyncId);
+ animate(frameTimeNs);
if (mNotifyWhenNoAnimation && !mLastRootAnimating) {
mService.mGlobalLock.notifyAll();
}
@@ -107,21 +104,11 @@ public class WindowAnimator {
};
}
- void addDisplayLocked(final int displayId) {
- // Create the DisplayContentsAnimator object by retrieving it if the associated
- // {@link DisplayContent} exists.
- getDisplayContentsAnimatorLocked(displayId);
- }
-
- void removeDisplayLocked(final int displayId) {
- mDisplayContentsAnimators.delete(displayId);
- }
-
void ready() {
mInitialized = true;
}
- private void animate(long frameTimeNs, long vsyncId) {
+ private void animate(long frameTimeNs) {
if (!mInitialized) {
return;
}
@@ -145,10 +132,9 @@ public class WindowAnimator {
final AccessibilityController accessibilityController =
mService.mAccessibilityController;
- final int numDisplays = mDisplayContentsAnimators.size();
+ final int numDisplays = root.getChildCount();
for (int i = 0; i < numDisplays; i++) {
- final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent dc = root.getDisplayContent(displayId);
+ final DisplayContent dc = root.getChildAt(i);
// Update animations of all applications, including those associated with
// exiting/removed apps.
dc.updateWindowsForAnimator();
@@ -156,12 +142,11 @@ public class WindowAnimator {
}
for (int i = 0; i < numDisplays; i++) {
- final int displayId = mDisplayContentsAnimators.keyAt(i);
- final DisplayContent dc = root.getDisplayContent(displayId);
+ final DisplayContent dc = root.getChildAt(i);
dc.checkAppWindowsReadyToShow();
if (accessibilityController.hasCallbacks()) {
- accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId,
+ accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
mTransaction);
}
}
@@ -237,12 +222,9 @@ public class WindowAnimator {
public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
final String subPrefix = " " + prefix;
- for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
- pw.print(prefix); pw.print("DisplayContentsAnimator #");
- pw.print(mDisplayContentsAnimators.keyAt(i));
- pw.println(":");
- final DisplayContent dc =
- mService.mRoot.getDisplayContent(mDisplayContentsAnimators.keyAt(i));
+ for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+ final DisplayContent dc = mService.mRoot.getChildAt(i);
+ pw.print(prefix); pw.print(dc); pw.println(":");
dc.dumpWindowAnimators(pw, subPrefix);
pw.println();
}
@@ -260,23 +242,6 @@ public class WindowAnimator {
}
}
- private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
- if (displayId < 0) {
- return null;
- }
-
- DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
-
- // It is possible that this underlying {@link DisplayContent} has been removed. In this
- // case, we do not want to create an animator associated with it as {link #animate} will
- // fail.
- if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) {
- displayAnimator = new DisplayContentsAnimator();
- mDisplayContentsAnimators.put(displayId, displayAnimator);
- }
- return displayAnimator;
- }
-
void scheduleAnimation() {
if (!mAnimationFrameCallbackScheduled) {
mAnimationFrameCallbackScheduled = true;
@@ -291,9 +256,6 @@ public class WindowAnimator {
}
}
- private class DisplayContentsAnimator {
- }
-
boolean isAnimationScheduled() {
return mAnimationFrameCallbackScheduled;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 14c826d71af7..a7a90604f228 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,7 +88,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
@@ -8537,13 +8536,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- // focus-transfer can re-order windows and thus potentially causes visible changes:
- final Transition transition = mAtmService.getTransitionController()
- .requestTransitionIfNeeded(TRANSIT_TO_FRONT, task);
mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
- if (transition != null) {
- transition.setReady(task, true /* ready */);
- }
}
/**
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index e65b9f39e31c..531a6bdc0130 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -160,12 +160,10 @@ public final class CredentialManagerService
int resolvedUserId, boolean disabled, String[] serviceNames) {
getOrConstructSystemServiceListLock(resolvedUserId);
if (serviceNames == null || serviceNames.length == 0) {
- Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
return new ArrayList<>();
}
List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
for (String serviceName : serviceNames) {
- Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
if (TextUtils.isEmpty(serviceName)) {
continue;
}
@@ -173,7 +171,7 @@ public final class CredentialManagerService
serviceList.add(
new CredentialManagerServiceImpl(this, mLock, resolvedUserId, serviceName));
} catch (PackageManager.NameNotFoundException | SecurityException e) {
- Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
+ Slog.e(TAG, "Unable to add serviceInfo : ", e);
}
}
return serviceList;
@@ -423,7 +421,7 @@ public final class CredentialManagerService
userId);
callingAppInfo = new CallingAppInfo(realPackageName, packageInfo.signingInfo, origin);
} catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
+ Slog.e(TAG, "Issue while retrieving signatureInfo : ", e);
callingAppInfo = new CallingAppInfo(realPackageName, null, origin);
}
return callingAppInfo;
@@ -436,7 +434,8 @@ public final class CredentialManagerService
IGetCredentialCallback callback,
final String callingPackage) {
final long timestampBegan = System.nanoTime();
- Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
+ Slog.d(TAG, "starting executeGetCredential with callingPackage: "
+ + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
if (request.getOrigin() != null) {
@@ -630,11 +629,10 @@ public final class CredentialManagerService
GetCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available on this device.");
} catch (RemoteException e) {
- Log.i(
+ Slog.e(
TAG,
"Issue invoking onError on IGetCredentialCallback "
- + "callback: "
- + e.getMessage());
+ + "callback: ", e);
}
}
@@ -649,7 +647,7 @@ public final class CredentialManagerService
ICreateCredentialCallback callback,
String callingPackage) {
final long timestampBegan = System.nanoTime();
- Log.i(TAG, "starting executeCreateCredential with callingPackage: "
+ Slog.d(TAG, "starting executeCreateCredential with callingPackage: "
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
@@ -692,11 +690,10 @@ public final class CredentialManagerService
CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"No create options available.");
} catch (RemoteException e) {
- Log.i(
+ Slog.e(
TAG,
"Issue invoking onError on ICreateCredentialCallback "
- + "callback: "
- + e.getMessage());
+ + "callback: ", e);
}
}
@@ -712,21 +709,19 @@ public final class CredentialManagerService
MetricUtilities.logApiCalledInitialPhase(initMetric,
session.mRequestSessionMetric.returnIncrementSequence());
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Log.w(TAG, "Unexpected error during metric logging: ", e);
}
}
@Override
public void setEnabledProviders(
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
- Log.i(TAG, "setEnabledProviders");
-
if (!hasWriteSecureSettingsPermission()) {
try {
callback.onError(
PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
} catch (RemoteException e) {
- Log.e(TAG, "Issue with invoking response: " + e.getMessage());
+ Slog.e(TAG, "Issue with invoking response: ", e);
}
return;
}
@@ -753,7 +748,7 @@ public final class CredentialManagerService
"failed_setting_store",
"Failed to store setting containing enabled providers");
} catch (RemoteException e) {
- Log.i(TAG, "Issue with invoking error response: " + e.getMessage());
+ Slog.e(TAG, "Issue with invoking error response: ", e);
return;
}
}
@@ -762,7 +757,7 @@ public final class CredentialManagerService
try {
callback.onResponse();
} catch (RemoteException e) {
- Log.i(TAG, "Issue with invoking response: " + e.getMessage());
+ Slog.e(TAG, "Issue with invoking response: ", e);
// TODO: Propagate failure
}
@@ -774,7 +769,8 @@ public final class CredentialManagerService
@Override
public boolean isEnabledCredentialProviderService(
ComponentName componentName, String callingPackage) {
- Log.i(TAG, "isEnabledCredentialProviderService");
+ Slog.d(TAG, "isEnabledCredentialProviderService with componentName: "
+ + componentName.flattenToString());
// TODO(253157366): Check additional set of services.
final int userId = UserHandle.getCallingUserId();
@@ -792,10 +788,10 @@ public final class CredentialManagerService
MetricUtilities.logApiCalledSimpleV1(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
ApiStatus.FAILURE, callingUid);
- Log.w(
+ Slog.w(
TAG,
- "isEnabledCredentialProviderService: Component name does not"
- + " match package name.");
+ "isEnabledCredentialProviderService: Component name does "
+ + "not match package name.");
return false;
}
MetricUtilities.logApiCalledSimpleV1(
@@ -813,7 +809,6 @@ public final class CredentialManagerService
@Override
public List<CredentialProviderInfo> getCredentialProviderServices(
int userId, int providerFilter) {
- Log.i(TAG, "getCredentialProviderServices");
verifyGetProvidersPermission();
return CredentialProviderInfoFactory.getCredentialProviderServices(
@@ -823,7 +818,6 @@ public final class CredentialManagerService
@Override
public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
int providerFilter) {
- Log.i(TAG, "getCredentialProviderServicesForTesting");
verifyGetProvidersPermission();
final int userId = UserHandle.getCallingUserId();
@@ -844,8 +838,8 @@ public final class CredentialManagerService
.getServiceInfo().getComponentName());
} catch (NullPointerException e) {
// Safe check
- Log.i(TAG, "Skipping provider as either the providerInfo"
- + "or serviceInfo is null - weird");
+ Slog.e(TAG, "Skipping provider as either the providerInfo"
+ + " or serviceInfo is null - weird");
}
});
}
@@ -858,7 +852,8 @@ public final class CredentialManagerService
IClearCredentialStateCallback callback,
String callingPackage) {
final long timestampBegan = System.nanoTime();
- Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
+ Slog.d(TAG, "starting clearCredentialState with callingPackage: "
+ + callingPackage);
final int userId = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
@@ -885,13 +880,13 @@ public final class CredentialManagerService
if (providerSessions.isEmpty()) {
try {
// TODO("Replace with properly defined error type")
- callback.onError("UNKNOWN", "No crdentials available on this " + "device");
+ callback.onError("UNKNOWN", "No credentials available on "
+ + "this device");
} catch (RemoteException e) {
- Log.i(
+ Slog.e(
TAG,
"Issue invoking onError on IClearCredentialStateCallback "
- + "callback: "
- + e.getMessage());
+ + "callback: ", e);
}
}
@@ -906,7 +901,7 @@ public final class CredentialManagerService
public void registerCredentialDescription(
RegisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException, NonCredentialProviderCallerException {
- Log.i(TAG, "registerCredentialDescription");
+ Slog.d(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException();
@@ -924,7 +919,9 @@ public final class CredentialManagerService
public void unregisterCredentialDescription(
UnregisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException {
- Log.i(TAG, "registerCredentialDescription");
+ Slog.d(TAG, "unregisterCredentialDescription with callingPackage: "
+ + callingPackage);
+
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index ee55a1ccc357..91be2a734e85 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -23,7 +23,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderInfoFactory;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -41,14 +40,15 @@ public final class CredentialManagerServiceImpl extends
// TODO(b/210531) : Make final when update flow is fixed
@GuardedBy("mLock")
- @NonNull private CredentialProviderInfo mInfo;
+ @NonNull
+ private CredentialProviderInfo mInfo;
CredentialManagerServiceImpl(
@NonNull CredentialManagerService master,
@NonNull Object lock, int userId, String serviceName)
throws PackageManager.NameNotFoundException {
super(master, lock, userId);
- Log.i(TAG, "in CredentialManagerServiceImpl constructed with: " + serviceName);
+ Slog.d(TAG, "CredentialManagerServiceImpl constructed for: " + serviceName);
synchronized (mLock) {
newServiceInfoLocked(ComponentName.unflattenFromString(serviceName));
}
@@ -63,10 +63,8 @@ public final class CredentialManagerServiceImpl extends
@NonNull CredentialManagerService master,
@NonNull Object lock, int userId, CredentialProviderInfo providerInfo) {
super(master, lock, userId);
- Log.i(TAG, "in CredentialManagerServiceImpl constructed with system constructor: "
- + providerInfo.isSystemProvider()
- + " , " + providerInfo.getServiceInfo() == null ? "" :
- providerInfo.getServiceInfo().getComponentName().flattenToString());
+ Slog.d(TAG, "CredentialManagerServiceImpl constructed for: "
+ + providerInfo.getServiceInfo().getComponentName().flattenToString());
mInfo = providerInfo;
}
@@ -76,12 +74,12 @@ public final class CredentialManagerServiceImpl extends
throws PackageManager.NameNotFoundException {
// TODO : Test update flows with multiple providers
if (mInfo != null) {
- Log.i(TAG, "newServiceInfoLocked with : "
+ Slog.d(TAG, "newServiceInfoLocked, mInfo not null : "
+ mInfo.getServiceInfo().getComponentName().flattenToString() + " , "
- + serviceComponent.getPackageName());
+ + serviceComponent.flattenToString());
} else {
- Log.i(TAG, "newServiceInfoLocked with null mInfo , "
- + serviceComponent.getPackageName());
+ Slog.d(TAG, "newServiceInfoLocked, mInfo null, "
+ + serviceComponent.flattenToString());
}
mInfo = CredentialProviderInfoFactory.create(
getContext(), serviceComponent,
@@ -90,18 +88,18 @@ public final class CredentialManagerServiceImpl extends
}
/**
- * Starts a provider session and associates it with the given request session. */
+ * Starts a provider session and associates it with the given request session.
+ */
@Nullable
@GuardedBy("mLock")
public ProviderSession initiateProviderSessionForRequestLocked(
RequestSession requestSession, List<String> requestOptions) {
if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
- Log.i(TAG, "Service is not capable");
+ Slog.d(TAG, "Service does not have the required capabilities");
return null;
}
- Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
if (mInfo == null) {
- Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+ Slog.w(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+ "but mInfo is null. This shouldn't happen");
return null;
}
@@ -114,15 +112,11 @@ public final class CredentialManagerServiceImpl extends
@GuardedBy("mLock")
boolean isServiceCapableLocked(List<String> requestedOptions) {
if (mInfo == null) {
- Slog.i(TAG, "in isServiceCapable, mInfo is null");
return false;
}
for (String capability : requestedOptions) {
if (mInfo.hasCapability(capability)) {
- Slog.i(TAG, "Provider can handle: " + capability);
return true;
- } else {
- Slog.i(TAG, "Provider cannot handle: " + capability);
}
}
return false;
@@ -146,7 +140,7 @@ public final class CredentialManagerServiceImpl extends
try {
newServiceInfoLocked(mInfo.getServiceInfo().getComponentName());
} catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Issue while updating serviceInfo: " + e.getMessage());
+ Slog.e(TAG, "Issue while updating serviceInfo: " + e.getMessage());
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2479646e4561..770e728ba935 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10617,38 +10617,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* - SYSTEM_UID
* - adb unless hasIncompatibleAccountsOrNonAdb is true.
*/
+ @GuardedBy("getLockObject()")
private void enforceCanSetProfileOwnerLocked(
- CallerIdentity caller, @Nullable ComponentName owner, int userHandle,
+ CallerIdentity caller, @Nullable ComponentName owner, @UserIdInt int userId,
boolean hasIncompatibleAccountsOrNonAdb) {
- UserInfo info = getUserInfo(userHandle);
+ UserInfo info = getUserInfo(userId);
if (info == null) {
// User doesn't exist.
throw new IllegalArgumentException(
- "Attempted to set profile owner for invalid userId: " + userHandle);
+ "Attempted to set profile owner for invalid userId: " + userId);
}
if (info.isGuest()) {
throw new IllegalStateException("Cannot set a profile owner on a guest");
}
- if (mOwners.hasProfileOwner(userHandle)) {
- throw new IllegalStateException("Trying to set the profile owner, but profile owner "
- + "is already set.");
+ if (mOwners.hasProfileOwner(userId)) {
+ StringBuilder errorMessage = new StringBuilder("Trying to set the profile owner");
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ append(errorMessage, owner).append(" on user ").append(userId);
+ }
+ errorMessage.append(", but profile owner");
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ appendProfileOwnerLocked(errorMessage, userId);
+ }
+
+ throw new IllegalStateException(errorMessage.append(" is already set.").toString());
}
- if (mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerUserId() == userHandle) {
- throw new IllegalStateException("Trying to set the profile owner, but the user "
- + "already has a device owner.");
+ if (mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerUserId() == userId) {
+ StringBuilder errorMessage = new StringBuilder("Trying to set the profile owner");
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ append(errorMessage, owner).append(" on user ").append(userId);
+ }
+ errorMessage.append(", but the user already has a device owner");
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ appendDeviceOwnerLocked(errorMessage);
+ }
+ throw new IllegalStateException(errorMessage.append('.').toString());
}
if (isAdb(caller)) {
- if ((mIsWatch || hasUserSetupCompleted(userHandle))
+ if ((mIsWatch || hasUserSetupCompleted(userId))
&& hasIncompatibleAccountsOrNonAdb) {
- throw new IllegalStateException("Not allowed to set the profile owner because "
- + "there are already some accounts on the profile");
+ StringBuilder errorMessage = new StringBuilder("Not allowed to set the profile "
+ + "owner");
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ append(errorMessage, owner).append(" on user ").append(userId).append(' ');
+ }
+ throw new IllegalStateException(errorMessage.append(" because there are already "
+ + "some accounts on the profile.").toString());
}
return;
}
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
- if ((mIsWatch || hasUserSetupCompleted(userHandle))) {
+ if ((mIsWatch || hasUserSetupCompleted(userId))) {
Preconditions.checkState(isSystemUid(caller),
"Cannot set the profile owner on a user which is already set-up");
@@ -10665,31 +10686,62 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
* permission.
*/
+ @GuardedBy("getLockObject()")
private void enforceCanSetDeviceOwnerLocked(
CallerIdentity caller, @Nullable ComponentName owner, @UserIdInt int deviceOwnerUserId,
boolean hasIncompatibleAccountsOrNonAdb) {
+ boolean showComponentOnError = false;
if (!isAdb(caller)) {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ } else {
+ showComponentOnError = true;
}
final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner,
/* deviceOwnerUserId= */ deviceOwnerUserId, /* callingUserId*/ caller.getUserId(),
isAdb(caller), hasIncompatibleAccountsOrNonAdb);
if (code != STATUS_OK) {
- throw new IllegalStateException(
- computeProvisioningErrorString(code, deviceOwnerUserId));
+ throw new IllegalStateException(computeProvisioningErrorStringLocked(code,
+ deviceOwnerUserId, owner, showComponentOnError));
}
}
- private static String computeProvisioningErrorString(int code, @UserIdInt int userId) {
+ private String computeProvisioningErrorString(int code, @UserIdInt int userId) {
+ synchronized (getLockObject()) {
+ return computeProvisioningErrorStringLocked(code, userId, /* newOwner= */ null,
+ /* showComponentOnError= */ false);
+ }
+ }
+
+ @GuardedBy("getLockObject()")
+ private String computeProvisioningErrorStringLocked(int code, @UserIdInt int userId,
+ @Nullable ComponentName newOwner, boolean showComponentOnError) {
switch (code) {
case STATUS_OK:
return "OK";
- case STATUS_HAS_DEVICE_OWNER:
- return "Trying to set the device owner, but device owner is already set.";
- case STATUS_USER_HAS_PROFILE_OWNER:
- return "Trying to set the device owner, but the user already has a profile owner.";
+ case STATUS_HAS_DEVICE_OWNER: {
+ StringBuilder error = new StringBuilder("Trying to set the device owner");
+ if (showComponentOnError && newOwner != null) {
+ append(error, newOwner);
+ }
+ error.append(", but device owner");
+ if (showComponentOnError) {
+ appendDeviceOwnerLocked(error);
+ }
+ return error.append(" is already set.").toString();
+ }
+ case STATUS_USER_HAS_PROFILE_OWNER: {
+ StringBuilder error = new StringBuilder("Trying to set the device owner");
+ if (showComponentOnError && newOwner != null) {
+ append(error, newOwner);
+ }
+ error.append(", but the user already has a profile owner");
+ if (showComponentOnError) {
+ appendProfileOwnerLocked(error, userId);
+ }
+ return error.append(".").toString();
+ }
case STATUS_USER_NOT_RUNNING:
return "User " + userId + " not running.";
case STATUS_NOT_SYSTEM_USER:
@@ -10708,7 +10760,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
default:
return "Unexpected @ProvisioningPreCondition: " + code;
}
+ }
+ @GuardedBy("getLockObject()")
+ private void appendDeviceOwnerLocked(StringBuilder string) {
+ ComponentName deviceOwner = getDeviceOwnerComponent(/* callingUserOnly= */ false);
+ if (deviceOwner == null) {
+ // Shouldn't happen, but it doesn't hurt to check...
+ Slogf.wtf(LOG_TAG, "appendDeviceOwnerLocked(): device has no DO set");
+ return;
+ }
+ append(string, deviceOwner);
+ }
+
+ @GuardedBy("getLockObject()")
+ private void appendProfileOwnerLocked(StringBuilder string, @UserIdInt int userId) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner == null) {
+ // Shouldn't happen, but it doesn't hurt to check...
+ Slogf.wtf(LOG_TAG, "profileOwner(%d): PO not set", userId);
+ return;
+ }
+ append(string, profileOwner);
+ }
+
+ private static StringBuilder append(StringBuilder string, ComponentName component) {
+ return string.append(" (").append(component.flattenToShortString()).append(')');
}
private void enforceUserUnlocked(int userId) {
@@ -19654,7 +19731,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
- public void setApplicationExemptions(String packageName, int[] exemptions) {
+ public void setApplicationExemptions(String callerPackage, String packageName,
+ int[] exemptions) {
if (!mHasFeature) {
return;
}
@@ -19665,7 +19743,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
final ApplicationInfo packageInfo;
packageInfo = getPackageInfoWithNullCheck(packageName, caller);
@@ -22186,7 +22264,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_SAFE_BOOT,
- MANAGE_DEVICE_POLICY_TIME);
+ MANAGE_DEVICE_POLICY_TIME,
+ MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
List.of(
MANAGE_DEVICE_POLICY_ACROSS_USERS,
@@ -22292,7 +22371,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
MANAGE_DEVICE_POLICY_STATUS_BAR,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS);
+ MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
+ MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of(
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -22431,8 +22511,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
@@ -22587,6 +22665,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
callerPackageName);
}
+
return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
}
@@ -22627,7 +22706,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
// Check the permission for the role-holder
if (isCallerDevicePolicyManagementRoleHolder(caller)) {
- return anyDpcHasPermission(permission, mContext.getUserId());
+ return anyDpcHasPermission(permission, caller.getUserId());
}
if (DELEGATE_SCOPES.containsKey(permission)) {
return isCallerDelegate(caller, DELEGATE_SCOPES.get(permission));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 2d8fa1bcd8cd..2180a781e437 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -21,11 +21,13 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
@@ -50,6 +52,7 @@ import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -81,6 +84,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
import java.time.Clock;
import java.time.ZoneOffset;
@@ -322,6 +326,109 @@ public class ConnectivityControllerTest {
}
@Test
+ public void testMeteredAllowed() throws Exception {
+ final JobInfo.Builder jobBuilder = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus job = spy(createJobStatus(jobBuilder));
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ // Unmetered network is always "metered allowed"
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .build();
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ // Temporarily unmetered network is always "metered allowed"
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .build();
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ // Respond with the default values in NetworkPolicyManager. If those ever change enough
+ // to cause these tests to fail, we would likely need to go and update
+ // ConnectivityController.
+ doAnswer(
+ (Answer<Integer>) invocationOnMock
+ -> NetworkPolicyManager.getDefaultProcessNetworkCapabilities(
+ invocationOnMock.getArgument(0)))
+ .when(mService).getUidCapabilities(anyInt());
+
+ // Foreground is always allowed for metered network
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build();
+
+ when(mService.getUidProcState(anyInt()))
+ .thenReturn(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+
+ when(mService.getUidProcState(anyInt()))
+ .thenReturn(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+
+ when(mService.getUidProcState(anyInt())).thenReturn(ActivityManager.PROCESS_STATE_TOP);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+
+ when(mService.getUidProcState(anyInt())).thenReturn(JobInfo.BIAS_DEFAULT);
+ when(job.getFlags()).thenReturn(JobInfo.FLAG_WILL_BE_FOREGROUND);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ when(mService.getUidProcState(anyInt())).thenReturn(ActivityManager.PROCESS_STATE_UNKNOWN);
+ when(job.getFlags()).thenReturn(0);
+
+ // User initiated is always allowed for metered network
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build();
+ when(job.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ // Background non-user-initiated should follow the app's restricted state
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build();
+ when(job.shouldTreatAsUserInitiatedJob()).thenReturn(false);
+ when(mNetPolicyManager.getRestrictBackgroundStatus(anyInt()))
+ .thenReturn(ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ // Test cache
+ when(mNetPolicyManager.getRestrictBackgroundStatus(anyInt()))
+ .thenReturn(ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ // Clear cache
+ controller.onAppRemovedLocked(job.getSourcePackageName(), job.getSourceUid());
+ assertFalse(controller.isSatisfied(job, net, caps, mConstants));
+ // Test cache
+ when(mNetPolicyManager.getRestrictBackgroundStatus(anyInt()))
+ .thenReturn(ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED);
+ assertFalse(controller.isSatisfied(job, net, caps, mConstants));
+ // Clear cache
+ controller.onAppRemovedLocked(job.getSourcePackageName(), job.getSourceUid());
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+ }
+
+ @Test
public void testStrongEnough_Cellular() {
mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 8994a488bd56..ab8f3f2279fe 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -383,6 +383,7 @@ public class UserControllerTest {
// Call dispatchUserSwitch and verify that observer was called only once
mInjector.mHandler.clearAllRecordedMessages();
mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
Set<Integer> expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG);
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
@@ -413,6 +414,7 @@ public class UserControllerTest {
// Call dispatchUserSwitch and verify that observer was called only once
mInjector.mHandler.clearAllRecordedMessages();
mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
// Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout)
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 13371cce5fb5..40ecaf1770a9 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -54,6 +54,7 @@ import android.util.Xml;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -264,7 +265,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_ID, DEFAULT_LOCALES, false);
+ DEFAULT_USER_ID, DEFAULT_LOCALES, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
}
@@ -280,7 +282,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_ID, DEFAULT_LOCALES, false);
+ DEFAULT_USER_ID, DEFAULT_LOCALES, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false,
@@ -303,7 +306,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+ DEFAULT_USER_ID, DEFAULT_LOCALES, true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
@@ -327,7 +331,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
- DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES, true,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
@@ -369,7 +374,8 @@ public class LocaleManagerBackupRestoreTest {
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsA), true);
+ LocaleList.forLanguageTags(langTagsA), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
pkgLocalesMap.remove(pkgNameA);
@@ -422,11 +428,12 @@ public class LocaleManagerBackupRestoreTest {
// Restore locales only for myAppB.
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsB), true);
+ LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
// App C is staged.
pkgLocalesMap.remove(pkgNameA);
@@ -484,7 +491,8 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsA), false);
+ LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
@@ -499,7 +507,8 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsB), true);
+ LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
@@ -606,7 +615,8 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
- pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false);
+ pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
pkgLocalesMap.remove(pkgNameA);
@@ -620,7 +630,7 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
checkStageDataDoesNotExist(DEFAULT_USER_ID);
}
@@ -734,7 +744,7 @@ public class LocaleManagerBackupRestoreTest {
*/
private void verifyNothingRestored() throws Exception {
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
}
private static void verifyPayloadForAppLocales(Map<String, LocalesInfo> expectedPkgLocalesMap,
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 07fda309f03e..550204b99323 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.PackageConfig;
@@ -136,7 +137,8 @@ public class LocaleManagerServiceTest {
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- LocaleList.getEmptyLocaleList(), false);
+ LocaleList.getEmptyLocaleList(), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected SecurityException");
} finally {
verify(mMockContext).enforceCallingOrSelfPermission(
@@ -151,7 +153,8 @@ public class LocaleManagerServiceTest {
public void testSetApplicationLocales_nullPackageName_fails() throws Exception {
try {
mLocaleManagerService.setApplicationLocales(/* appPackageName = */ null,
- DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false);
+ DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected NullPointerException");
} finally {
verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -165,7 +168,8 @@ public class LocaleManagerServiceTest {
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- /* locales = */ null, false);
+ /* locales = */ null, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected NullPointerException");
} finally {
verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -183,7 +187,8 @@ public class LocaleManagerServiceTest {
setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- DEFAULT_LOCALES, true);
+ DEFAULT_LOCALES, true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DELEGATE);
assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -196,7 +201,8 @@ public class LocaleManagerServiceTest {
.when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- DEFAULT_LOCALES, false);
+ DEFAULT_LOCALES, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -208,7 +214,8 @@ public class LocaleManagerServiceTest {
.when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- LocaleList.getEmptyLocaleList(), false);
+ LocaleList.getEmptyLocaleList(), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected IllegalArgumentException");
} finally {
assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
index 32c9e75e1288..697f4d46d16a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
@@ -107,7 +107,7 @@ public class UserManagerServiceShellCommandTest {
new String[]{"get-main-user"}, mShellCallback, mResultReceiver));
mWriter.flush();
- assertEquals("Main user id: 12", mOutStream.toString().trim());
+ assertEquals("12", mOutStream.toString().trim());
}
@Test
@@ -118,7 +118,7 @@ public class UserManagerServiceShellCommandTest {
assertEquals(1, mCommand.exec(mBinder, in, out, err,
new String[]{"get-main-user"}, mShellCallback, mResultReceiver));
mWriter.flush();
- assertEquals("Couldn't get main user.", mOutStream.toString().trim());
+ assertEquals("None", mOutStream.toString().trim());
}
@Test
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index e8e3a8f84f21..09ee59816a2c 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -32,6 +32,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index 182bf949af1f..82bc6f6c5263 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -16,8 +16,10 @@ package com.android.server;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.Intent;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.Build;
@@ -44,8 +46,8 @@ public class UiServiceTestCase {
protected static final String PKG_R = "com.example.r";
@Rule
- public final TestableContext mContext =
- new TestableContext(InstrumentationRegistry.getContext(), null);
+ public TestableContext mContext =
+ spy(new TestableContext(InstrumentationRegistry.getContext(), null));
protected TestableContext getContext() {
return mContext;
@@ -81,6 +83,11 @@ public class UiServiceTestCase {
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
when(mUgmInternal.checkGrantUriPermission(
anyInt(), anyString(), any(Uri.class), anyInt(), anyInt())).thenReturn(-1);
+
+ Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser(
+ any(), any(), any(), any(), any());
+ Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
+ Mockito.doNothing().when(mContext).unregisterReceiver(any());
}
@After
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index ce076217f37b..8fcbf2f9e97a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -130,13 +130,18 @@ public class ManagedServicesTest extends UiServiceTestCase {
private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary;
private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary;
+ private UserHandle mUser;
+ private String mPkg;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- getContext().setMockPackageManager(mPm);
- getContext().addMockSystemService(Context.USER_SERVICE, mUm);
- getContext().addMockSystemService(DEVICE_POLICY_SERVICE, mDpm);
+ mContext.setMockPackageManager(mPm);
+ mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.addMockSystemService(DEVICE_POLICY_SERVICE, mDpm);
+ mUser = mContext.getUser();
+ mPkg = mContext.getPackageName();
List<UserInfo> users = new ArrayList<>();
users.add(mZero);
@@ -861,8 +866,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -891,8 +896,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -921,8 +926,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -951,8 +956,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -981,8 +986,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1011,8 +1016,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1437,8 +1442,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1464,8 +1469,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1492,8 +1497,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1522,8 +1527,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1552,8 +1557,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1791,8 +1796,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1837,8 +1842,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
@@ -1880,8 +1885,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
ApplicationInfo ai = new ApplicationInfo();
ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- when(context.getPackageName()).thenReturn(mContext.getPackageName());
- when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageName()).thenReturn(mPkg);
+ when(context.getUserId()).thenReturn(mUser.getIdentifier());
when(context.getPackageManager()).thenReturn(pm);
when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index d73a3b8e44a6..95fae0707304 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -33,9 +33,11 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.UserHandle;
@@ -63,7 +65,7 @@ import java.util.List;
@SmallTest
@RunWith(Parameterized.class)
public class NotificationComparatorTest extends UiServiceTestCase {
- @Mock Context mContext;
+ @Mock Context mMockContext;
@Mock TelecomManager mTm;
@Mock RankingHandler handler;
@Mock PackageManager mPm;
@@ -115,32 +117,35 @@ public class NotificationComparatorTest extends UiServiceTestCase {
int userId = UserHandle.myUserId();
- when(mContext.getResources()).thenReturn(getContext().getResources());
- when(mContext.getTheme()).thenReturn(getContext().getTheme());
- when(mContext.getContentResolver()).thenReturn(getContext().getContentResolver());
- when(mContext.getPackageManager()).thenReturn(mPm);
- when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm);
- when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator);
- when(mContext.getString(anyInt())).thenCallRealMethod();
- when(mContext.getColor(anyInt())).thenCallRealMethod();
+ final Resources res = mContext.getResources();
+ when(mMockContext.getResources()).thenReturn(res);
+ final Resources.Theme theme = mContext.getTheme();
+ when(mMockContext.getTheme()).thenReturn(theme);
+ final ContentResolver cr = mContext.getContentResolver();
+ when(mMockContext.getContentResolver()).thenReturn(cr);
+ when(mMockContext.getPackageManager()).thenReturn(mPm);
+ when(mMockContext.getSystemService(eq(mMockContext.TELECOM_SERVICE))).thenReturn(mTm);
+ when(mMockContext.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+ when(mMockContext.getString(anyInt())).thenCallRealMethod();
+ when(mMockContext.getColor(anyInt())).thenCallRealMethod();
when(mTm.getDefaultDialerPackage()).thenReturn(callPkg);
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
try {
when(mPm.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn(legacy);
- when(mContext.getApplicationInfo()).thenReturn(legacy);
+ when(mMockContext.getApplicationInfo()).thenReturn(legacy);
} catch (PackageManager.NameNotFoundException e) {
// let's hope not
}
- smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
+ smsPkg = Settings.Secure.getString(mMockContext.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION);
- Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification nonInterruptiveNotif = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
- mRecordMinCallNonInterruptive = new NotificationRecord(mContext,
+ mRecordMinCallNonInterruptive = new NotificationRecord(mMockContext,
new StatusBarNotification(callPkg,
callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
nonInterruptiveNotif,
@@ -148,134 +153,134 @@ public class NotificationComparatorTest extends UiServiceTestCase {
mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
mRecordMinCallNonInterruptive.setInterruptive(false);
- Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n1 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
- mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
+ mRecordMinCall = new NotificationRecord(mMockContext, new StatusBarNotification(callPkg,
callPkg, 1, "minCall", callUid, callUid, n1,
new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
mRecordMinCall.setInterruptive(true);
- Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n2 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
- mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
+ mRecordHighCall = new NotificationRecord(mMockContext, new StatusBarNotification(callPkg,
callPkg, 1, "highcall", callUid, callUid, n2,
new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification nHighCallStyle = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification nHighCallStyle = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setStyle(Notification.CallStyle.forOngoingCall(
new Person.Builder().setName("caller").build(),
mock(PendingIntent.class)
))
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
- mRecordHighCallStyle = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, 1, "highCallStyle", callUid, callUid, nHighCallStyle,
+ mRecordHighCallStyle = new NotificationRecord(mMockContext, new StatusBarNotification(
+ callPkg, callPkg, 1, "highCallStyle", callUid, callUid, nHighCallStyle,
new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordHighCallStyle.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
mRecordHighCallStyle.setInterruptive(true);
- Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n4 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setStyle(new Notification.MessagingStyle("sender!")).build();
- mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ mRecordInlineReply = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
"", 1599), getDefaultChannel());
mRecordInlineReply.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX);
if (smsPkg != null) {
- Notification n5 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n5 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_MESSAGE).build();
- mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg,
+ mRecordSms = new NotificationRecord(mMockContext, new StatusBarNotification(smsPkg,
smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
"", 1299), getDefaultChannel());
mRecordSms.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
}
- Notification n6 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
- mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ Notification n6 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build();
+ mRecordStarredContact = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId),
"", 1259), getDefaultChannel());
mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT);
mRecordStarredContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
- Notification n7 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
- mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ Notification n7 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build();
+ mRecordContact = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId),
"", 1259), getDefaultChannel());
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
- Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
- mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg,
+ Notification nSystemMax = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build();
+ mRecordSystemMax = new NotificationRecord(mMockContext, new StatusBarNotification(sysPkg,
sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId),
"", 1244), getDefaultChannel());
mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
- mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ Notification n8 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build();
+ mRecordUrgent = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
"", 1258), getDefaultChannel());
mRecordUrgent.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n9 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n9 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_MESSAGE)
.setFlag(Notification.FLAG_ONGOING_EVENT
|Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
- mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ mRecordCheater = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
"", 9258), getDefaultChannel());
mRecordCheater.setSystemImportance(NotificationManager.IMPORTANCE_LOW);
mRecordCheater.setPackagePriority(Notification.PRIORITY_MAX);
- Notification n10 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n10 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setStyle(new Notification.InboxStyle().setSummaryText("message!")).build();
- mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ mRecordEmail = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId),
"", 1599), getDefaultChannel());
mRecordEmail.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n11 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n11 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_MESSAGE)
.setColorized(true).setColor(Color.WHITE)
.build();
- mRecordCheaterColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, 1, "cheaterColorized", uid2, uid2, n11, new UserHandle(userId),
- "", 9258), getDefaultChannel());
+ mRecordCheaterColorized = new NotificationRecord(mMockContext,
+ new StatusBarNotification(pkg2,pkg2, 1, "cheaterColorized", uid2, uid2, n11,
+ new UserHandle(userId), "", 9258), getDefaultChannel());
mRecordCheaterColorized.setSystemImportance(NotificationManager.IMPORTANCE_LOW);
- Notification n12 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n12 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_MESSAGE)
.setColorized(true).setColor(Color.WHITE)
.setStyle(new Notification.MediaStyle())
.build();
- mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification(
+ mNoMediaSessionMedia = new NotificationRecord(mMockContext, new StatusBarNotification(
pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId),
"", 9258), getDefaultChannel());
mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
- Notification n13 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n13 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.setColorized(true).setColor(Color.WHITE)
.build();
- mRecordColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
+ mRecordColorized = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2,
pkg2, 1, "colorized", uid2, uid2, n13,
new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordColorized.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n14 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ Notification n14 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
.setCategory(Notification.CATEGORY_CALL)
.setColorized(true).setColor(Color.WHITE)
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
- mRecordColorizedCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, 1, "colorizedCall", callUid, callUid, n14,
+ mRecordColorizedCall = new NotificationRecord(mMockContext, new StatusBarNotification(
+ callPkg, callPkg, 1, "colorizedCall", callUid, callUid, n14,
new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordColorizedCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
}
@@ -316,14 +321,14 @@ public class NotificationComparatorTest extends UiServiceTestCase {
actual.addAll(expected);
Collections.shuffle(actual);
- Collections.sort(actual, new NotificationComparator(mContext));
+ Collections.sort(actual, new NotificationComparator(mMockContext));
assertThat(actual).containsExactlyElementsIn(expected).inOrder();
}
@Test
public void testRankingScoreOverrides() {
- NotificationComparator comp = new NotificationComparator(mContext);
+ NotificationComparator comp = new NotificationComparator(mMockContext);
NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
if (mSortByInterruptiveness) {
assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
@@ -339,7 +344,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
@Test
public void testMessaging() {
- NotificationComparator comp = new NotificationComparator(mContext);
+ NotificationComparator comp = new NotificationComparator(mMockContext);
assertTrue(comp.isImportantMessaging(mRecordInlineReply));
if (mRecordSms != null) {
assertTrue(comp.isImportantMessaging(mRecordSms));
@@ -350,7 +355,7 @@ public class NotificationComparatorTest extends UiServiceTestCase {
@Test
public void testPeople() {
- NotificationComparator comp = new NotificationComparator(mContext);
+ NotificationComparator comp = new NotificationComparator(mMockContext);
assertTrue(comp.isImportantPeople(mRecordStarredContact));
assertTrue(comp.isImportantPeople(mRecordContact));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 1b42fd3bb241..60f1e66b7e94 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -30,6 +30,7 @@ import android.app.NotificationHistory.HistoricalNotification;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Handler;
+import android.os.UserHandle;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
@@ -56,8 +57,6 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
File mRootDir;
@Mock
Handler mFileWriteHandler;
- @Mock
- Context mContext;
NotificationHistoryDatabase mDataBase;
@@ -92,10 +91,8 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mContext.getUser()).thenReturn(getContext().getUser());
- when(mContext.getPackageName()).thenReturn(getContext().getPackageName());
-
- mRootDir = new File(mContext.getFilesDir(), "NotificationHistoryDatabaseTest");
+ final File fileDir = mContext.getFilesDir();
+ mRootDir = new File(fileDir, "NotificationHistoryDatabaseTest");
mDataBase = new NotificationHistoryDatabase(mFileWriteHandler, mRootDir);
mDataBase.init();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9ca8d8444df9..42d1ace37ba5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -307,7 +307,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
private PermissionHelper mPermissionHelper;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
- private TestableContext mContext = spy(getContext());
private final String PKG = mContext.getPackageName();
private TestableLooper mTestableLooper;
@Mock
@@ -425,7 +424,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Shell permisssions will override permissions of our app, so add all necessary permissions
// for this test here:
InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
- "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG",
+ "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG",
"android.permission.READ_DEVICE_CONFIG",
"android.permission.READ_CONTACTS");
@@ -578,9 +577,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
ArgumentCaptor<IntentFilter> intentFilterCaptor =
ArgumentCaptor.forClass(IntentFilter.class);
- Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser(
- any(), any(), any(), any(), any());
- Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(),
any(), intentFilterCaptor.capture(), any(), any());
verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(),
@@ -611,6 +607,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
anyString(), anyInt(), any())).thenReturn(true);
when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
+ mockIsUserVisible(DEFAULT_DISPLAY, true);
mockIsVisibleBackgroundUsersSupported(false);
// Set the testable bubble extractor
@@ -6913,6 +6910,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testTextToastsCallStatusBar_nonUiContext_secondaryDisplay()
throws Exception {
allowTestPackageToToast();
+ mockIsUserVisible(SECONDARY_DISPLAY_ID, true);
enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
@@ -6936,6 +6934,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_secondaryDisplay()
throws Exception {
mockIsVisibleBackgroundUsersSupported(true);
+ mockIsUserVisible(SECONDARY_DISPLAY_ID, true);
mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
allowTestPackageToToast();
@@ -6960,6 +6959,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_secondaryDisplay()
throws Exception {
mockIsVisibleBackgroundUsersSupported(true);
+ mockIsUserVisible(SECONDARY_DISPLAY_ID, true);
mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
allowTestPackageToToast();
@@ -6969,6 +6969,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testTextToastsCallStatusBar_userNotVisibleOnDisplay() throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+ mockIsUserVisible(DEFAULT_DISPLAY, false);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ // enqueue toast -> no toasts enqueued
+ enqueueTextToast(testPackage, "Text");
+ verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
+ anyInt());
+ assertEquals(0, mService.mToastQueue.size());
+ }
+
+ @Test
public void testDisallowToastsFromSuspendedPackages() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
@@ -6985,6 +7005,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// enqueue toast -> no toasts enqueued
enqueueToast(testPackage, new TestableToastCallback());
+ verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
+ anyInt());
assertEquals(0, mService.mToastQueue.size());
}
@@ -10808,6 +10830,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
}
+ private void mockIsUserVisible(int displayId, boolean visible) {
+ when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
+ }
+
private void mockDisplayAssignedToUser(int displayId) {
when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 25e74bf5dcd2..fae92d9ac738 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -55,6 +55,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
@@ -126,7 +127,8 @@ public class NotificationRecordTest extends UiServiceTestCase {
MockitoAnnotations.initMocks(this);
when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator);
- when(mMockContext.getResources()).thenReturn(getContext().getResources());
+ final Resources res = mContext.getResources();
+ when(mMockContext.getResources()).thenReturn(res);
when(mMockContext.getPackageManager()).thenReturn(mPm);
when(mMockContext.getContentResolver()).thenReturn(mContentResolver);
ApplicationInfo appInfo = new ApplicationInfo();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
index fcff228fb591..0222bfbf8605 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java
@@ -67,7 +67,6 @@ import java.util.List;
public class NotificationShellCmdTest extends UiServiceTestCase {
private final Binder mBinder = new Binder();
private final ShellCallback mCallback = new ShellCallback();
- private final TestableContext mTestableContext = spy(getContext());
@Mock
NotificationManagerService mMockService;
@Mock
@@ -82,7 +81,7 @@ public class NotificationShellCmdTest extends UiServiceTestCase {
mTestableLooper = TestableLooper.get(this);
mResultReceiver = new ResultReceiver(new Handler(mTestableLooper.getLooper()));
- when(mMockService.getContext()).thenReturn(mTestableContext);
+ when(mMockService.getContext()).thenReturn(mContext);
when(mMockService.getBinderService()).thenReturn(mMockBinderService);
}
@@ -116,9 +115,10 @@ public class NotificationShellCmdTest extends UiServiceTestCase {
Notification captureNotification(String aTag) throws Exception {
ArgumentCaptor<Notification> notificationCaptor =
ArgumentCaptor.forClass(Notification.class);
+ final String pkg = getContext().getPackageName();
verify(mMockBinderService).enqueueNotificationWithTag(
- eq(getContext().getPackageName()),
- eq(getContext().getPackageName()),
+ eq(pkg),
+ eq(pkg),
eq(aTag),
eq(NotificationShellCmd.NOTIFICATION_ID),
notificationCaptor.capture(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index bf836ae0eba0..6f9798ea7d69 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -153,7 +153,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
private Resources mResources;
private TestableLooper mTestableLooper;
private ZenModeHelper mZenModeHelperSpy;
- private Context mContext;
private ContentResolver mContentResolver;
@Mock AppOpsManager mAppOps;
private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
@@ -163,9 +162,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
- mContext = spy(getContext());
mContentResolver = mContext.getContentResolver();
mResources = spy(mContext.getResources());
+ String pkg = mContext.getPackageName();
try {
when(mResources.getXml(R.xml.default_zen_mode_config)).thenReturn(
getDefaultConfigParser());
@@ -190,7 +189,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt()))
.thenReturn(CUSTOM_PKG_UID);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
- new String[] {getContext().getPackageName()});
+ new String[] {pkg});
mZenModeHelperSpy.mPm = mPackageManager;
}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
index 8694094ce6ac..4d3c26f4973e 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
@@ -37,6 +37,7 @@ import android.os.BatteryStatsInternal;
import android.os.Process;
import android.os.RemoteException;
+import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.FakeLatencyTracker;
@@ -93,10 +94,12 @@ public class SoundTriggerMiddlewareLoggingTest {
}
@Test
+ @FlakyTest(bugId = 275113847)
public void testSetUpAndTearDown() {
}
@Test
+ @FlakyTest(bugId = 275113847)
public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
throws RemoteException {
ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
@@ -112,6 +115,7 @@ public class SoundTriggerMiddlewareLoggingTest {
}
@Test
+ @FlakyTest(bugId = 275113847)
public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
ISoundTriggerCallback.class);
@@ -131,6 +135,7 @@ public class SoundTriggerMiddlewareLoggingTest {
}
@Test
+ @FlakyTest(bugId = 275113847)
public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
throws RemoteException {
ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
@@ -147,6 +152,7 @@ public class SoundTriggerMiddlewareLoggingTest {
}
@Test
+ @FlakyTest(bugId = 275113847)
public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
throws RemoteException {
ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 49af2c1dc681..5863e9d9243a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -20,6 +20,7 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_SLEEP;
import android.provider.Settings;
import android.view.Display;
@@ -49,6 +50,32 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
}
/**
+ * Power single press to start dreaming when so configured.
+ */
+ @Test
+ public void testPowerSinglePressRequestsDream() {
+ mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_DREAM_OR_SLEEP);
+ mPhoneWindowManager.overrideCanStartDreaming(true);
+ sendKey(KEYCODE_POWER);
+ mPhoneWindowManager.assertDreamRequest();
+ mPhoneWindowManager.assertLockedAfterAppTransitionFinished();
+ }
+
+ /**
+ * Power double-press to launch camera does not lock device when the single press behavior is to
+ * dream.
+ */
+ @Test
+ public void testPowerDoublePressWillNotLockDevice() {
+ mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_DREAM_OR_SLEEP);
+ mPhoneWindowManager.overrideCanStartDreaming(false);
+ sendKey(KEYCODE_POWER);
+ sendKey(KEYCODE_POWER);
+ mPhoneWindowManager.assertCameraLaunch();
+ mPhoneWindowManager.assertWillNotLockAfterAppTransitionFinished();
+ }
+
+ /**
* Power double press to trigger camera.
*/
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index b6939747a7b6..a2ee8a45433d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -84,6 +84,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.DisplayPolicy;
import com.android.server.wm.DisplayRotation;
import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import junit.framework.Assert;
@@ -289,6 +290,10 @@ class TestPhoneWindowManager {
}
}
+ void overrideShortPressOnPower(int behavior) {
+ mPhoneWindowManager.mShortPressOnPowerBehavior = behavior;
+ }
+
// Override assist perform function.
void overrideLongPressOnPower(int behavior) {
mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
@@ -311,6 +316,10 @@ class TestPhoneWindowManager {
}
}
+ void overrideCanStartDreaming(boolean canDream) {
+ doReturn(canDream).when(mDreamManagerInternal).canStartDreaming(anyBoolean());
+ }
+
void overrideDisplayState(int state) {
doReturn(state).when(mDisplay).getState();
Mockito.reset(mPowerManager);
@@ -374,6 +383,10 @@ class TestPhoneWindowManager {
timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
}
+ void assertDreamRequest() {
+ verify(mDreamManagerInternal).requestDream();
+ }
+
void assertPowerSleep() {
waitForIdle();
verify(mPowerManager,
@@ -454,4 +467,17 @@ class TestPhoneWindowManager {
waitForIdle();
verify(mInputManagerInternal).toggleCapsLock(anyInt());
}
+
+ void assertWillNotLockAfterAppTransitionFinished() {
+ Assert.assertFalse(mPhoneWindowManager.mLockAfterAppTransitionFinished);
+ }
+
+ void assertLockedAfterAppTransitionFinished() {
+ ArgumentCaptor<AppTransitionListener> transitionCaptor =
+ ArgumentCaptor.forClass(AppTransitionListener.class);
+ verify(mWindowManagerInternal).registerAppTransitionListener(
+ transitionCaptor.capture());
+ transitionCaptor.getValue().onAppTransitionFinishedLocked(any());
+ verify(mPhoneWindowManager).lockNow(null);
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 3eabea67e890..48a39e682340 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -621,8 +621,13 @@ final class HotwordDetectionConnection {
ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed,
int detectionServiceType) {
mIntent = intent;
- mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
mDetectionServiceType = detectionServiceType;
+ int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
+ if (mVisualQueryDetectionComponentName != null
+ && mHotwordDetectionComponentName != null) {
+ flags |= Context.BIND_SHARED_ISOLATED_PROCESS;
+ }
+ mBindingFlags = flags;
}
ServiceConnection createLocked() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 929e033315f7..62be2a555bc4 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -738,6 +738,13 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
} else {
verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
}
+ if (!verifyProcessSharingLocked()) {
+ Slog.w(TAG, "Sandboxed detection service not in shared isolated process");
+ throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService "
+ + "not in a shared isolated process. Please make sure to set "
+ + "android:allowSharedIsolatedProcess and android:isolatedProcess to be true "
+ + "and android:externalService to be false in the manifest file");
+ }
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
@@ -931,6 +938,19 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
+ @GuardedBy("this")
+ boolean verifyProcessSharingLocked() {
+ // only check this if both VQDS and HDS are declared in the app
+ ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
+ ServiceInfo visualQueryInfo =
+ getServiceInfoLocked(mVisualQueryDetectionComponentName, mUser);
+ if (hotwordInfo == null || visualQueryInfo == null) {
+ return true;
+ }
+ return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0
+ && (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
+ }
+
void forceRestartHotwordDetector() {
if (mHotwordDetectionConnection == null) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d2431f1cebf2..7abae1854025 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5761,6 +5761,57 @@ public class CarrierConfigManager {
public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY =
KEY_PREFIX + "capability_type_presence_uce_int_array";
+ /**
+ * Specifies the policy for disabling NR SA mode. Default value is
+ *{@link #SA_DISABLE_POLICY_NONE}.
+ * The value set as below:
+ * <ul>
+ * <li>0: {@link #SA_DISABLE_POLICY_NONE }</li>
+ * <li>1: {@link #SA_DISABLE_POLICY_WFC_ESTABLISHED }</li>
+ * <li>2: {@link #SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED }</li>
+ * <li>3: {@link #SA_DISABLE_POLICY_VOWIFI_REGISTERED }</li>
+ * </ul>
+ * @hide
+ */
+ public static final String KEY_SA_DISABLE_POLICY_INT = KEY_PREFIX + "sa_disable_policy_int";
+
+ /** @hide */
+ @IntDef({
+ SA_DISABLE_POLICY_NONE,
+ SA_DISABLE_POLICY_WFC_ESTABLISHED,
+ SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED,
+ SA_DISABLE_POLICY_VOWIFI_REGISTERED
+ })
+ public @interface NrSaDisablePolicy {}
+
+ /**
+ * Do not disables NR SA mode.
+ * @hide
+ */
+ public static final int SA_DISABLE_POLICY_NONE = 0;
+
+ /**
+ * Disables NR SA mode when VoWiFi call is established in order to improve the delay or
+ * voice mute when the handover from ePDG to NR is not supported in UE or network.
+ * @hide
+ */
+ public static final int SA_DISABLE_POLICY_WFC_ESTABLISHED = 1;
+
+ /**
+ * Disables NR SA mode when VoWiFi call is established when VoNR is disabled in order to
+ * improve the delay or voice mute when the handover from ePDG to NR is not supported
+ * in UE or network.
+ * @hide
+ */
+ public static final int SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED = 2;
+
+ /**
+ * Disables NR SA mode when IMS is registered over WiFi in order to improve the delay or
+ * voice mute when the handover from ePDG to NR is not supported in UE or network.
+ * @hide
+ */
+ public static final int SA_DISABLE_POLICY_VOWIFI_REGISTERED = 3;
+
private Ims() {}
private static PersistableBundle getDefaults() {
@@ -5832,6 +5883,7 @@ public class CarrierConfigManager {
defaults.putInt(KEY_REGISTRATION_RETRY_BASE_TIMER_MILLIS_INT, 30000);
defaults.putInt(KEY_REGISTRATION_RETRY_MAX_TIMER_MILLIS_INT, 1800000);
defaults.putInt(KEY_REGISTRATION_SUBSCRIBE_EXPIRY_TIMER_SEC_INT, 600000);
+ defaults.putInt(KEY_SA_DISABLE_POLICY_INT, SA_DISABLE_POLICY_NONE);
defaults.putIntArray(
KEY_IPSEC_AUTHENTICATION_ALGORITHMS_INT_ARRAY,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 286e71c14726..78c61964edfd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3652,17 +3652,10 @@ public class SubscriptionManager {
}
/**
- * Enables or disables a subscription. This is currently used in the settings page. It will
- * fail and return false if operation is not supported or failed.
+ * Enable or disable a subscription. This method is same as
+ * {@link #setUiccApplicationsEnabled(int, boolean)}.
*
- * To disable an active subscription on a physical (non-Euicc) SIM,
- * {@link #canDisablePhysicalSubscription} needs to be true.
- *
- * <p>
- * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
- *
- * @param subscriptionId Subscription to be enabled or disabled. It could be a eSIM or pSIM
- * subscription.
+ * @param subscriptionId Subscription to be enabled or disabled.
* @param enable whether user is turning it on or off.
*
* @return whether the operation is successful.
@@ -3672,19 +3665,15 @@ public class SubscriptionManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public boolean setSubscriptionEnabled(int subscriptionId, boolean enable) {
- if (VDBG) {
- logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
- }
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- return iSub.setSubscriptionEnabled(enable, subscriptionId);
+ iSub.setUiccApplicationsEnabled(enable, subscriptionId);
}
} catch (RemoteException ex) {
- // ignore it
+ return false;
}
-
- return false;
+ return true;
}
/**
@@ -3707,11 +3696,7 @@ public class SubscriptionManager {
logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled);
}
try {
- ISub iSub = ISub.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getSubscriptionServiceRegisterer()
- .get());
+ ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
iSub.setUiccApplicationsEnabled(enabled, subscriptionId);
}
@@ -3739,11 +3724,7 @@ public class SubscriptionManager {
logd("canDisablePhysicalSubscription");
}
try {
- ISub iSub = ISub.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getSubscriptionServiceRegisterer()
- .get());
+ ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
return iSub.canDisablePhysicalSubscription();
}
@@ -3867,10 +3848,15 @@ public class SubscriptionManager {
}
/**
- * DO NOT USE.
- * This API is designed for features that are not finished at this point. Do not call this API.
+ * Get the active subscription id by logical SIM slot index.
+ *
+ * @param slotIndex The logical SIM slot index.
+ * @return The active subscription id.
+ *
+ * @throws IllegalArgumentException if the provided slot index is invalid.
+ * @throws SecurityException if callers do not hold the required permission.
+ *
* @hide
- * TODO b/135547512: further clean up
*/
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 554beb9a35ba..1ce85ba93d95 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1186,7 +1186,6 @@ public class ApnSetting implements Parcelable {
ApnSetting other = (ApnSetting) o;
return mEntryName.equals(other.mEntryName)
- && Objects.equals(mId, other.mId)
&& Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
&& Objects.equals(mApnName, other.mApnName)
&& Objects.equals(mProxyAddress, other.mProxyAddress)
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 632a6874b5f5..6a5380ddb36e 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -265,8 +265,6 @@ interface ISub {
String getSubscriptionProperty(int subId, String propKey, String callingPackage,
String callingFeatureId);
- boolean setSubscriptionEnabled(boolean enable, int subId);
-
boolean isSubscriptionEnabled(int subId);
int getEnabledSubscriptionId(int slotIndex);
@@ -277,7 +275,7 @@ interface ISub {
boolean canDisablePhysicalSubscription();
- int setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
+ void setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
int setDeviceToDeviceStatusSharing(int sharing, int subId);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index d72f5288d4d5..6066d2e74209 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -66,7 +66,7 @@ open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(fli
flicker.assertLayers {
this.isVisible(ComponentNameMatcher.IME)
.then()
- .isVisible(ComponentNameMatcher.IME_SNAPSHOT)
+ .isVisible(ComponentNameMatcher.IME_SNAPSHOT, isOptional = true)
.then()
.isInvisible(ComponentNameMatcher.IME_SNAPSHOT, isOptional = true)
.isInvisible(ComponentNameMatcher.IME)
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
index 0f9663442740..7419ee1230d3 100644
--- a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -16,15 +16,15 @@
package com.android.internal.os;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.platform.test.annotations.Presubmit;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -40,7 +40,7 @@ public class TimeoutRecordTest {
@Test
public void forBroadcastReceiver_returnsCorrectTimeoutRecord() {
Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+ intent.setComponent(new ComponentName("com.example.app", "com.example.app.ExampleClass"));
TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent);
@@ -48,14 +48,28 @@ public class TimeoutRecordTest {
assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
assertEquals(record.mReason,
"Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
- + ".app/ExampleClass }");
+ + ".app/.ExampleClass }");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forBroadcastReceiver_withPackageAndClass_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent,
+ "com.example.app", "com.example.app.ExampleClass");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/.ExampleClass }");
assertTrue(record.mEndTakenBeforeLocks);
}
@Test
public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() {
Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+ intent.setComponent(new ComponentName("com.example.app", "com.example.app.ExampleClass"));
TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L);
@@ -63,7 +77,7 @@ public class TimeoutRecordTest {
assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
assertEquals(record.mReason,
"Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
- + ".app/ExampleClass }, waited 1000ms");
+ + ".app/.ExampleClass }, waited 1000ms");
assertTrue(record.mEndTakenBeforeLocks);
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 15fd817ba73b..e5ef62b16dfd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -38,6 +38,7 @@ import android.os.RemoteException;
import android.util.Log;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import java.util.HashMap;
import java.util.List;
@@ -49,9 +50,14 @@ import java.util.concurrent.Executor;
* This class is the library used by consumers of Shared Connectivity data to bind to the service,
* receive callbacks from, and send user actions to the service.
*
+ * A client must register at least one callback so that the manager will bind to the service. Once
+ * all callbacks are unregistered, the manager will unbind from the service. When the client no
+ * longer needs Shared Connectivity data, the client must unregister.
+ *
* The methods {@link #connectHotspotNetwork}, {@link #disconnectHotspotNetwork},
* {@link #connectKnownNetwork} and {@link #forgetKnownNetwork} are not valid and will return false
- * if not called between {@link SharedConnectivityClientCallback#onServiceConnected()}
+ * and getter methods will fail and return null if not called between
+ * {@link SharedConnectivityClientCallback#onServiceConnected()}
* and {@link SharedConnectivityClientCallback#onServiceDisconnected()} or if
* {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} was called.
*
@@ -139,19 +145,22 @@ public class SharedConnectivityManager {
}
private ISharedConnectivityService mService;
+ @GuardedBy("mProxyDataLock")
private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy>
mProxyMap = new HashMap<>();
+ @GuardedBy("mProxyDataLock")
private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy>
mCallbackProxyCache = new HashMap<>();
- // Used for testing
- private final ServiceConnection mServiceConnection;
+ // Makes sure mProxyMap and mCallbackProxyCache are locked together when one of them is used.
+ private final Object mProxyDataLock = new Object();
+ private final Context mContext;
+ private final String mServicePackageName;
+ private final String mIntentAction;
+ private ServiceConnection mServiceConnection;
/**
* Creates a new instance of {@link SharedConnectivityManager}.
*
- * Automatically binds to implementation of {@link SharedConnectivityService} specified in
- * the device overlay.
- *
* @return An instance of {@link SharedConnectivityManager} or null if the shared connectivity
* service is not found.
* @hide
@@ -185,12 +194,18 @@ public class SharedConnectivityManager {
private SharedConnectivityManager(@NonNull Context context, String servicePackageName,
String serviceIntentAction) {
+ mContext = context;
+ mServicePackageName = servicePackageName;
+ mIntentAction = serviceIntentAction;
+ }
+
+ private void bind() {
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ISharedConnectivityService.Stub.asInterface(service);
- if (!mCallbackProxyCache.isEmpty()) {
- synchronized (mCallbackProxyCache) {
+ synchronized (mProxyDataLock) {
+ if (!mCallbackProxyCache.isEmpty()) {
mCallbackProxyCache.keySet().forEach(callback ->
registerCallbackInternal(
callback, mCallbackProxyCache.get(callback)));
@@ -203,15 +218,13 @@ public class SharedConnectivityManager {
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Log.i(TAG, "onServiceDisconnected");
mService = null;
- if (!mCallbackProxyCache.isEmpty()) {
- synchronized (mCallbackProxyCache) {
+ synchronized (mProxyDataLock) {
+ if (!mCallbackProxyCache.isEmpty()) {
mCallbackProxyCache.keySet().forEach(
SharedConnectivityClientCallback::onServiceDisconnected);
mCallbackProxyCache.clear();
}
- }
- if (!mProxyMap.isEmpty()) {
- synchronized (mProxyMap) {
+ if (!mProxyMap.isEmpty()) {
mProxyMap.keySet().forEach(
SharedConnectivityClientCallback::onServiceDisconnected);
mProxyMap.clear();
@@ -220,8 +233,8 @@ public class SharedConnectivityManager {
}
};
- context.bindService(
- new Intent().setPackage(servicePackageName).setAction(serviceIntentAction),
+ mContext.bindService(
+ new Intent().setPackage(mServicePackageName).setAction(mIntentAction),
mServiceConnection, Context.BIND_AUTO_CREATE);
}
@@ -229,7 +242,7 @@ public class SharedConnectivityManager {
SharedConnectivityCallbackProxy proxy) {
try {
mService.registerCallback(proxy);
- synchronized (mProxyMap) {
+ synchronized (mProxyDataLock) {
mProxyMap.put(callback, proxy);
}
callback.onServiceConnected();
@@ -256,10 +269,19 @@ public class SharedConnectivityManager {
return mServiceConnection;
}
+ private void unbind() {
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+ }
+
/**
* Registers a callback for receiving updates to the list of Hotspot Networks, Known Networks,
* shared connectivity settings state, hotspot network connection status and known network
* connection status.
+ * Automatically binds to implementation of {@link SharedConnectivityService} specified in
+ * the device overlay when the first callback is registered.
* The {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} will be called if the
* registration failed.
*
@@ -284,9 +306,16 @@ public class SharedConnectivityManager {
SharedConnectivityCallbackProxy proxy =
new SharedConnectivityCallbackProxy(executor, callback);
if (mService == null) {
- synchronized (mCallbackProxyCache) {
+ boolean shouldBind;
+ synchronized (mProxyDataLock) {
+ // Size can be 1 in different cases of register/unregister sequences. If size is 0
+ // Bind never happened or unbind was called.
+ shouldBind = mCallbackProxyCache.size() == 0;
mCallbackProxyCache.put(callback, proxy);
}
+ if (shouldBind) {
+ bind();
+ }
return;
}
registerCallbackInternal(callback, proxy);
@@ -294,6 +323,7 @@ public class SharedConnectivityManager {
/**
* Unregisters a callback.
+ * Unbinds from {@link SharedConnectivityService} when no more callbacks are registered.
*
* @return Returns true if the callback was successfully unregistered, false otherwise.
*/
@@ -309,16 +339,27 @@ public class SharedConnectivityManager {
}
if (mService == null) {
- synchronized (mCallbackProxyCache) {
+ boolean shouldUnbind;
+ synchronized (mProxyDataLock) {
mCallbackProxyCache.remove(callback);
+ // Connection was never established, so all registered callbacks are in the cache.
+ shouldUnbind = mCallbackProxyCache.isEmpty();
+ }
+ if (shouldUnbind) {
+ unbind();
}
return true;
}
try {
- mService.unregisterCallback(mProxyMap.get(callback));
- synchronized (mProxyMap) {
+ boolean shouldUnbind;
+ synchronized (mProxyDataLock) {
+ mService.unregisterCallback(mProxyMap.get(callback));
mProxyMap.remove(callback);
+ shouldUnbind = mProxyMap.isEmpty();
+ }
+ if (shouldUnbind) {
+ unbind();
}
} catch (RemoteException e) {
Log.e(TAG, "Exception in unregisterCallback", e);
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 96afe278e3e0..b585bd5cfd7b 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -28,15 +28,17 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.res.Resources;
import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService;
import android.os.Bundle;
-import android.os.Parcel;
import android.os.RemoteException;
import androidx.test.filters.SmallTest;
@@ -80,7 +82,7 @@ public class SharedConnectivityManagerTest {
@Mock
Executor mExecutor;
@Mock
- SharedConnectivityClientCallback mClientCallback;
+ SharedConnectivityClientCallback mClientCallback, mClientCallback2;
@Mock
Resources mResources;
@Mock
@@ -95,47 +97,52 @@ public class SharedConnectivityManagerTest {
setResources(mContext);
}
- /**
- * Verifies constructor is binding to service.
- */
@Test
- public void bindingToService() {
- SharedConnectivityManager.create(mContext);
+ public void resourcesNotDefined_createShouldReturnNull() {
+ when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException());
- verify(mContext).bindService(any(), any(), anyInt());
+ assertThat(SharedConnectivityManager.create(mContext)).isNull();
}
- /**
- * Verifies create method returns null when resources are not specified
- */
@Test
- public void resourcesNotDefined() {
- when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException());
+ public void bindingToServiceOnFirstCallbackRegistration() {
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+ manager.registerCallback(mExecutor, mClientCallback);
- assertThat(SharedConnectivityManager.create(mContext)).isNull();
+ verify(mContext).bindService(any(Intent.class), any(ServiceConnection.class), anyInt());
}
- /**
- * Verifies registerCallback behavior.
- */
@Test
- public void registerCallback_serviceNotConnected_registrationCachedThenConnected()
- throws Exception {
+ public void bindIsCalledOnceOnMultipleCallbackRegistrations() throws Exception {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
- manager.setService(null);
manager.registerCallback(mExecutor, mClientCallback);
- manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
+ verify(mContext, times(1)).bindService(any(Intent.class), any(ServiceConnection.class),
+ anyInt());
+
+ manager.registerCallback(mExecutor, mClientCallback2);
+ verify(mContext, times(1)).bindService(any(Intent.class), any(ServiceConnection.class),
+ anyInt());
+ }
- // Since the binder is embedded in a proxy class, the call to registerCallback is done on
- // the proxy. So instead verifying that the proxy is calling the binder.
- verify(mIBinder).transact(anyInt(), any(Parcel.class), any(Parcel.class), anyInt());
+ @Test
+ public void unbindIsCalledOnLastCallbackUnregistrations() throws Exception {
+ SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+
+ manager.registerCallback(mExecutor, mClientCallback);
+ manager.registerCallback(mExecutor, mClientCallback2);
+ manager.unregisterCallback(mClientCallback);
+ verify(mContext, never()).unbindService(
+ any(ServiceConnection.class));
+
+ manager.unregisterCallback(mClientCallback2);
+ verify(mContext, times(1)).unbindService(
+ any(ServiceConnection.class));
}
@Test
public void registerCallback_serviceNotConnected_canUnregisterAndReregister() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
- manager.setService(null);
manager.registerCallback(mExecutor, mClientCallback);
manager.unregisterCallback(mClientCallback);
@@ -177,9 +184,6 @@ public class SharedConnectivityManagerTest {
verify(mClientCallback).onRegisterCallbackFailed(any(RemoteException.class));
}
- /**
- * Verifies unregisterCallback behavior.
- */
@Test
public void unregisterCallback_withoutRegisteringFirst_serviceNotConnected_shouldFail() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
@@ -239,11 +243,8 @@ public class SharedConnectivityManagerTest {
assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
}
- /**
- * Verifies callback is called when service is connected
- */
@Test
- public void onServiceConnected_registerCallbackBeforeConnection() {
+ public void onServiceConnected() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.registerCallback(mExecutor, mClientCallback);
@@ -253,20 +254,7 @@ public class SharedConnectivityManagerTest {
}
@Test
- public void onServiceConnected_registerCallbackAfterConnection() {
- SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
-
- manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
- manager.registerCallback(mExecutor, mClientCallback);
-
- verify(mClientCallback).onServiceConnected();
- }
-
- /**
- * Verifies callback is called when service is disconnected
- */
- @Test
- public void onServiceDisconnected_registerCallbackBeforeConnection() {
+ public void onServiceDisconnected() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.registerCallback(mExecutor, mClientCallback);
@@ -276,20 +264,7 @@ public class SharedConnectivityManagerTest {
verify(mClientCallback).onServiceDisconnected();
}
- @Test
- public void onServiceDisconnected_registerCallbackAfterConnection() {
- SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
-
- manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
- manager.registerCallback(mExecutor, mClientCallback);
- manager.getServiceConnection().onServiceDisconnected(COMPONENT_NAME);
-
- verify(mClientCallback).onServiceDisconnected();
- }
- /**
- * Verifies connectHotspotNetwork behavior.
- */
@Test
public void connectHotspotNetwork_serviceNotConnected_shouldFail() {
HotspotNetwork network = buildHotspotNetwork();
@@ -320,9 +295,6 @@ public class SharedConnectivityManagerTest {
assertThat(manager.connectHotspotNetwork(network)).isFalse();
}
- /**
- * Verifies disconnectHotspotNetwork behavior.
- */
@Test
public void disconnectHotspotNetwork_serviceNotConnected_shouldFail() {
HotspotNetwork network = buildHotspotNetwork();
@@ -353,9 +325,6 @@ public class SharedConnectivityManagerTest {
assertThat(manager.disconnectHotspotNetwork(network)).isFalse();
}
- /**
- * Verifies connectKnownNetwork behavior.
- */
@Test
public void connectKnownNetwork_serviceNotConnected_shouldFail() throws RemoteException {
KnownNetwork network = buildKnownNetwork();
@@ -386,9 +355,6 @@ public class SharedConnectivityManagerTest {
assertThat(manager.connectKnownNetwork(network)).isFalse();
}
- /**
- * Verifies forgetKnownNetwork behavior.
- */
@Test
public void forgetKnownNetwork_serviceNotConnected_shouldFail() {
KnownNetwork network = buildKnownNetwork();
@@ -419,9 +385,6 @@ public class SharedConnectivityManagerTest {
assertThat(manager.forgetKnownNetwork(network)).isFalse();
}
- /**
- * Verify getters.
- */
@Test
public void getHotspotNetworks_serviceNotConnected_shouldReturnNull() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);