summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--api/current.txt87
-rw-r--r--api/system-current.txt25
-rw-r--r--api/test-current.txt1
-rw-r--r--cmds/statsd/src/StatsService.cpp12
-rw-r--r--cmds/statsd/src/atoms.proto11
-rw-r--r--cmds/statsd/src/external/SubsystemSleepStatePuller.cpp301
-rw-r--r--cmds/statsd/tests/StatsLogProcessor_test.cpp43
-rw-r--r--cmds/statsd/tools/localtools/Android.bp25
-rw-r--r--cmds/statsd/tools/localtools/localdrive_manifest.txt1
-rw-r--r--cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java119
-rw-r--r--cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java343
-rw-r--r--cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java (renamed from cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java)102
-rw-r--r--cmds/statsd/tools/localtools/testdrive_manifest.txt1
-rw-r--r--cmds/statsd/tools/statsd-testdrive/Android.bp11
-rw-r--r--cmds/statsd/tools/statsd-testdrive/manifest.txt1
-rw-r--r--config/hiddenapi-greylist.txt19
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/ActivityManagerInternal.java24
-rw-r--r--core/java/android/app/ApplicationPackageManager.java11
-rw-r--r--core/java/android/app/RecoverableSecurityException.java55
-rw-r--r--core/java/android/app/RemoteInput.java60
-rw-r--r--core/java/android/app/SystemServiceRegistry.java2
-rw-r--r--core/java/android/app/WindowConfiguration.java9
-rw-r--r--core/java/android/app/admin/DelegatedAdminReceiver.java129
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java8
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java186
-rw-r--r--core/java/android/app/admin/DevicePolicyManagerInternal.java6
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl9
-rw-r--r--core/java/android/app/admin/PasswordMetrics.java116
-rw-r--r--core/java/android/app/usage/EventList.java17
-rw-r--r--core/java/android/app/usage/UsageEvents.java80
-rw-r--r--core/java/android/app/usage/UsageStats.java387
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java7
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java19
-rw-r--r--core/java/android/content/pm/ModuleInfo.java146
-rw-r--r--core/java/android/content/pm/PackageInstaller.java11
-rw-r--r--core/java/android/content/pm/PackageManager.java131
-rw-r--r--core/java/android/content/pm/PackageParser.java6
-rw-r--r--core/java/android/hardware/biometrics/BiometricFaceConstants.java15
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java39
-rw-r--r--core/java/android/hardware/display/DisplayedContentSample.java94
-rw-r--r--core/java/android/hardware/display/DisplayedContentSamplingAttributes.java67
-rw-r--r--core/java/android/hardware/face/FaceManager.java19
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl8
-rw-r--r--core/java/android/os/Binder.java23
-rw-r--r--core/java/android/os/GraphicsEnvironment.java31
-rw-r--r--core/java/android/os/UserManager.java12
-rw-r--r--core/java/android/os/ZygoteProcess.java2
-rw-r--r--core/java/android/provider/CalendarContract.java80
-rw-r--r--core/java/android/service/carrier/CarrierIdentifier.java14
-rw-r--r--core/java/android/view/InputEventCompatProcessor.java81
-rw-r--r--core/java/android/view/SurfaceControl.java47
-rw-r--r--core/java/android/view/ViewRootImpl.java77
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java30
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl3
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java63
-rw-r--r--core/java/android/view/textclassifier/ActionsSuggestionsHelper.java31
-rw-r--r--core/java/android/view/textclassifier/ConversationActions.java71
-rw-r--r--core/java/android/view/textclassifier/TextClassifier.java8
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java28
-rw-r--r--core/java/com/android/internal/app/procstats/AssociationState.java15
-rw-r--r--core/java/com/android/internal/app/procstats/ProcessStats.java48
-rw-r--r--core/java/com/android/internal/os/Zygote.java5
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java4
-rw-r--r--core/java/com/android/server/SystemConfig.java755
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_media_AudioEffectDescriptor.cpp123
-rw-r--r--core/jni/android_media_AudioEffectDescriptor.h38
-rw-r--r--core/jni/android_util_Binder.cpp2
-rw-r--r--core/jni/android_util_Process.cpp2
-rw-r--r--core/jni/android_view_SurfaceControl.cpp98
-rw-r--r--core/jni/com_android_internal_net_NetworkStatsFactory.cpp1
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp62
-rw-r--r--core/proto/android/server/jobscheduler.proto8
-rw-r--r--core/proto/android/server/usagestatsservice.proto6
-rw-r--r--core/res/AndroidManifest.xml19
-rw-r--r--core/res/res/values/attrs_manifest.xml3
-rw-r--r--core/res/res/values/config.xml10
-rw-r--r--core/res/res/values/public.xml3
-rw-r--r--core/res/res/values/strings.xml9
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java129
-rw-r--r--core/tests/coretests/src/android/app/usage/UsageStatsTest.java426
-rw-r--r--core/tests/coretests/src/android/os/BinderWorkSourceTest.java4
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java112
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java2
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java5
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--libs/androidfw/OWNERS1
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h2
-rw-r--r--location/java/android/location/SettingInjectorService.java67
-rw-r--r--media/Android.bp36
-rw-r--r--media/java/android/media/AudioPresentation.java16
-rw-r--r--media/java/android/media/MediaCodec.java32
-rw-r--r--media/java/android/media/MediaMuxer.java7
-rw-r--r--media/java/android/media/MediaPlayer2.java5
-rw-r--r--media/java/android/media/MediaRecorder.java11
-rw-r--r--media/jni/android_media_MediaCodec.cpp22
-rw-r--r--media/jni/android_media_MediaCodec.h2
-rw-r--r--media/jni/audioeffect/android_media_AudioEffect.cpp162
-rw-r--r--media/jni/audioeffect/android_media_Visualizer.cpp1
-rw-r--r--native/android/libandroid.map.txt4
-rw-r--r--native/android/libandroid_net.map.txt9
-rw-r--r--native/android/net.c28
-rw-r--r--packages/CarSystemUI/Android.bp1
-rw-r--r--packages/CarSystemUI/res/values/config.xml27
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java161
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java5
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java6
-rw-r--r--packages/ExtServices/AndroidManifest.xml7
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java80
-rw-r--r--packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java80
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java236
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java35
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml1
-rw-r--r--packages/PackageInstaller/res/layout/uninstall_content_view.xml13
-rw-r--r--packages/PackageInstaller/res/values/strings.xml2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java3
-rwxr-xr-xpackages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java4
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java159
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java2
-rw-r--r--packages/SettingsLib/res/values/strings.xml4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java131
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java43
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java179
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java15
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java19
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java9
-rw-r--r--services/core/java/com/android/server/AnyMotionDetector.java9
-rw-r--r--services/core/java/com/android/server/DeviceIdleController.java76
-rw-r--r--services/core/java/com/android/server/OWNERS1
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java49
-rw-r--r--services/core/java/com/android/server/VibratorService.java36
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java23
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java216
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java45
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricServiceBase.java9
-rw-r--r--services/core/java/com/android/server/biometrics/EnrollClient.java13
-rw-r--r--services/core/java/com/android/server/biometrics/face/FaceService.java31
-rw-r--r--services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java5
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java45
-rw-r--r--services/core/java/com/android/server/display/OWNERS1
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java79
-rw-r--r--services/core/java/com/android/server/job/controllers/ConnectivityController.java216
-rw-r--r--services/core/java/com/android/server/job/controllers/JobStatus.java20
-rw-r--r--services/core/java/com/android/server/job/controllers/QuotaController.java85
-rw-r--r--services/core/java/com/android/server/job/controllers/StateController.java36
-rw-r--r--services/core/java/com/android/server/lights/OWNERS1
-rw-r--r--services/core/java/com/android/server/pm/DynamicCodeLoggingService.java115
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java74
-rw-r--r--services/core/java/com/android/server/pm/dex/DexLogger.java192
-rw-r--r--services/core/java/com/android/server/pm/dex/DexManager.java59
-rw-r--r--services/core/java/com/android/server/power/BatterySaverPolicy.java2
-rw-r--r--services/core/java/com/android/server/power/OWNERS1
-rw-r--r--services/core/java/com/android/server/signedconfig/SignedConfig.java37
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java55
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java132
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java25
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java13
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java17
-rw-r--r--services/core/java/com/android/server/wm/PinnedActivityStack.java2
-rw-r--r--services/core/java/com/android/server/wm/PinnedStackWindowController.java10
-rw-r--r--services/core/java/com/android/server/wm/RootActivityContainer.java9
-rw-r--r--services/core/java/com/android/server/wm/StackWindowController.java315
-rw-r--r--services/core/java/com/android/server/wm/Task.java77
-rw-r--r--services/core/java/com/android/server/wm/TaskRecord.java475
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskWindowContainerController.java262
-rw-r--r--services/core/java/com/android/server/wm/TaskWindowContainerListener.java33
-rw-r--r--services/core/jni/com_android_server_net_NetworkStatsService.cpp1
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java11
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java342
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java6
-rw-r--r--services/java/com/android/server/SystemServer.java13
-rw-r--r--services/net/java/android/net/apf/ApfFilter.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java581
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java152
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java159
-rw-r--r--services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java60
-rw-r--r--services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java313
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java209
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java67
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java74
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java136
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java145
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java67
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java79
-rw-r--r--services/usage/java/com/android/server/usage/AppStandbyController.java8
-rw-r--r--services/usage/java/com/android/server/usage/IntervalStats.java118
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsProto.java15
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java111
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsXmlV1.java29
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java246
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java11
-rw-r--r--telephony/java/android/provider/Telephony.java32
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java13
-rw-r--r--telephony/java/android/telephony/DataFailCause.java259
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java6
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java225
-rw-r--r--telephony/java/android/telephony/ims/RcsManager.java36
-rw-r--r--telephony/java/android/telephony/ims/RcsMessageStore.java (renamed from telephony/java/android/telephony/rcs/RcsManager.java)14
-rw-r--r--telephony/java/android/telephony/ims/RcsThread.aidl20
-rw-r--r--telephony/java/android/telephony/ims/RcsThread.java (renamed from telephony/java/android/telephony/rcs/RcsThread.java)5
-rw-r--r--telephony/java/android/telephony/ims/aidl/IRcs.aidl (renamed from telephony/java/com/android/internal/telephony/rcs/IRcs.aidl)8
-rw-r--r--telephony/java/android/telephony/rcs/RcsThread.aidl20
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl30
-rw-r--r--test-base/Android.bp2
-rw-r--r--test-runner/Android.bp2
-rw-r--r--test-runner/api/current.txt4
-rw-r--r--tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java186
-rw-r--r--tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java (renamed from tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java)8
-rw-r--r--tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java4
-rw-r--r--tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java7
-rw-r--r--tests/net/java/android/net/apf/ApfTest.java41
-rw-r--r--tests/net/jni/apf_jni.cpp101
-rw-r--r--tests/net/res/raw/apfPcap.pcapbin0 -> 101547 bytes
-rw-r--r--tools/aapt2/Debug.cpp25
-rw-r--r--tools/aapt2/ResourceParser.cpp124
-rw-r--r--tools/aapt2/ResourceParser.h4
-rw-r--r--tools/aapt2/ResourceParser_test.cpp110
-rw-r--r--tools/aapt2/ResourceTable.cpp37
-rw-r--r--tools/aapt2/ResourceTable.h33
-rw-r--r--tools/aapt2/ResourceTable_test.cpp73
-rw-r--r--tools/aapt2/Resources.proto17
-rw-r--r--tools/aapt2/cmd/Dump.h1
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp24
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp84
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp103
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp52
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp38
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp81
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp4
-rw-r--r--tools/aapt2/link/TableMerger.cpp38
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp62
-rw-r--r--tools/aapt2/split/TableSplitter.cpp2
-rw-r--r--tools/aapt2/test/Builders.cpp9
-rw-r--r--tools/aapt2/test/Builders.h4
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl2
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java15
-rw-r--r--wifi/java/com/android/server/wifi/AbstractWifiService.java5
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java13
264 files changed, 10608 insertions, 4424 deletions
diff --git a/Android.bp b/Android.bp
index 32bd40861839..e096c9df5662 100644
--- a/Android.bp
+++ b/Android.bp
@@ -535,6 +535,7 @@ java_defaults {
"telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl",
"telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl",
"telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl",
+ "telephony/java/android/telephony/ims/aidl/IRcs.aidl",
"telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
"telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
"telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl",
@@ -611,7 +612,6 @@ java_defaults {
"telephony/java/com/android/internal/telephony/euicc/ISetDefaultSmdpAddressCallback.aidl",
"telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl",
"telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl",
- "telephony/java/com/android/internal/telephony/rcs/IRcs.aidl",
"wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl",
"wifi/java/android/net/wifi/INetworkRequestUserSelectionCallback.aidl",
"wifi/java/android/net/wifi/ISoftApCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 725548302e4e..bb6b889c9a14 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -91,6 +91,7 @@ package android {
field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
+ field public static final java.lang.String CALL_COMPANION_APP = "android.permission.CALL_COMPANION_APP";
field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -703,6 +704,7 @@ package android {
field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
field public static final int hardwareAccelerated = 16843475; // 0x10102d3
field public static final int hasCode = 16842764; // 0x101000c
+ field public static final int hasFragileUserData = 16844192; // 0x10105a0
field public static final deprecated int headerAmPmTextAppearance = 16843936; // 0x10104a0
field public static final int headerBackground = 16843055; // 0x101012f
field public static final deprecated int headerDayOfMonthTextAppearance = 16843927; // 0x1010497
@@ -5949,6 +5951,15 @@ package android.app {
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
+ method public int describeContents();
+ method public android.app.RemoteAction getUserAction();
+ method public java.lang.CharSequence getUserMessage();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.RemoteAction clone();
@@ -5974,6 +5985,7 @@ package android.app {
method public java.util.Set<java.lang.String> getAllowedDataTypes();
method public java.lang.CharSequence[] getChoices();
method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String);
+ method public int getEditChoicesBeforeSending();
method public android.os.Bundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getResultKey();
@@ -5983,6 +5995,9 @@ package android.app {
method public static void setResultsSource(android.content.Intent, int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR;
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2
field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results";
field public static final int SOURCE_CHOICE = 1; // 0x1
@@ -5997,6 +6012,7 @@ package android.app {
method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean);
method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]);
+ method public android.app.RemoteInput.Builder setEditChoicesBeforeSending(int);
method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
}
@@ -6434,6 +6450,13 @@ package android.app.admin {
field public static final android.os.Parcelable.Creator<android.app.admin.ConnectEvent> CREATOR;
}
+ public class DelegatedAdminReceiver extends android.content.BroadcastReceiver {
+ ctor public DelegatedAdminReceiver();
+ method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String);
+ method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int);
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
public final class DeviceAdminInfo implements android.os.Parcelable {
ctor public DeviceAdminInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public int describeContents();
@@ -6496,11 +6519,13 @@ package android.app.admin {
method public void onUserStarted(android.content.Context, android.content.Intent, android.os.UserHandle);
method public void onUserStopped(android.content.Context, android.content.Intent, android.os.UserHandle);
method public void onUserSwitched(android.content.Context, android.content.Intent, android.os.UserHandle);
+ field public static final java.lang.String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
field public static final java.lang.String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING";
field public static final java.lang.String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING";
+ field public static final java.lang.String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE";
field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED";
field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING";
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
@@ -6574,6 +6599,7 @@ package android.app.admin {
method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
+ method public int getPasswordComplexity();
method public long getPasswordExpiration(android.content.ComponentName);
method public long getPasswordExpirationTimeout(android.content.ComponentName);
method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -6745,10 +6771,13 @@ package android.app.admin {
field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+ field public static final java.lang.String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
field public static final java.lang.String DELEGATION_INSTALL_EXISTING_PACKAGE = "delegation-install-existing-package";
field public static final java.lang.String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages";
+ field public static final java.lang.String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+ field public static final java.lang.String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation";
field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
@@ -6827,6 +6856,10 @@ package android.app.admin {
field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+ field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
+ field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
+ field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000
+ field public static final int PASSWORD_COMPLEXITY_NONE = 0; // 0x0
field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
field public static final int PASSWORD_QUALITY_BIOMETRIC_WEAK = 32768; // 0x8000
@@ -7615,13 +7648,16 @@ package android.app.usage {
method public java.lang.String getPackageName();
method public java.lang.String getShortcutId();
method public long getTimeStamp();
+ field public static final int ACTIVITY_PAUSED = 2; // 0x2
+ field public static final int ACTIVITY_RESUMED = 1; // 0x1
+ field public static final int ACTIVITY_STOPPED = 23; // 0x17
field public static final int CONFIGURATION_CHANGE = 5; // 0x5
field public static final int FOREGROUND_SERVICE_START = 19; // 0x13
field public static final int FOREGROUND_SERVICE_STOP = 20; // 0x14
field public static final int KEYGUARD_HIDDEN = 18; // 0x12
field public static final int KEYGUARD_SHOWN = 17; // 0x11
- field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
- field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
+ field public static final deprecated int MOVE_TO_BACKGROUND = 2; // 0x2
+ field public static final deprecated int MOVE_TO_FOREGROUND = 1; // 0x1
field public static final int NONE = 0; // 0x0
field public static final int SCREEN_INTERACTIVE = 15; // 0xf
field public static final int SCREEN_NON_INTERACTIVE = 16; // 0x10
@@ -7638,9 +7674,11 @@ package android.app.usage {
method public long getLastTimeForegroundServiceUsed();
method public long getLastTimeStamp();
method public long getLastTimeUsed();
+ method public long getLastTimeVisible();
method public java.lang.String getPackageName();
method public long getTotalTimeForegroundServiceUsed();
method public long getTotalTimeInForeground();
+ method public long getTotalTimeVisible();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.UsageStats> CREATOR;
}
@@ -11189,6 +11227,15 @@ package android.content.pm {
field public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1024; // 0x400
}
+ public final class ModuleInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getPackageName();
+ method public boolean isHidden();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.ModuleInfo> CREATOR;
+ }
+
public class PackageInfo implements android.os.Parcelable {
ctor public PackageInfo();
method public int describeContents();
@@ -11405,6 +11452,7 @@ package android.content.pm {
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+ method public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract byte[] getInstantAppCookie();
@@ -11412,6 +11460,7 @@ package android.content.pm {
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
+ method public android.content.pm.ModuleInfo getModuleInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract java.lang.String getNameForUid(int);
method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int);
method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -22829,9 +22878,10 @@ package android.location {
ctor public SettingInjectorService(java.lang.String);
method public final android.os.IBinder onBind(android.content.Intent);
method protected abstract boolean onGetEnabled();
- method protected abstract deprecated java.lang.String onGetSummary();
+ method protected abstract java.lang.String onGetSummary();
method public final void onStart(android.content.Intent, int);
method public final int onStartCommand(android.content.Intent, int, int);
+ method public static final void refreshSettings(android.content.Context);
field public static final java.lang.String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged";
field public static final java.lang.String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService";
field public static final java.lang.String ATTRIBUTES_NAME = "injected-location-setting";
@@ -23963,6 +24013,7 @@ package android.media {
method public void releaseOutputBuffer(int, boolean);
method public void releaseOutputBuffer(int, long);
method public void reset();
+ method public void setAudioPresentation(android.media.AudioPresentation);
method public void setCallback(android.media.MediaCodec.Callback, android.os.Handler);
method public void setCallback(android.media.MediaCodec.Callback);
method public void setInputSurface(android.view.Surface);
@@ -24953,6 +25004,7 @@ package android.media {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
+ field public static final int MUXER_OUTPUT_OGG = 4; // 0x4
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
@@ -25408,6 +25460,7 @@ package android.media {
field public static final int AMR_WB = 2; // 0x2
field public static final int DEFAULT = 0; // 0x0
field public static final int HE_AAC = 4; // 0x4
+ field public static final int OPUS = 7; // 0x7
field public static final int VORBIS = 6; // 0x6
}
@@ -25458,6 +25511,7 @@ package android.media {
field public static final int DEFAULT = 0; // 0x0
field public static final int MPEG_2_TS = 8; // 0x8
field public static final int MPEG_4 = 2; // 0x2
+ field public static final int OGG = 11; // 0xb
field public static final deprecated int RAW_AMR = 3; // 0x3
field public static final int THREE_GPP = 1; // 0x1
field public static final int WEBM = 9; // 0x9
@@ -35936,9 +35990,11 @@ package android.provider {
}
public final class CalendarContract {
+ method public static boolean startViewCalendarEventInManagedProfile(android.content.Context, long, long, long, boolean, int);
field public static final java.lang.String ACCOUNT_TYPE_LOCAL = "LOCAL";
field public static final java.lang.String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER";
field public static final java.lang.String ACTION_HANDLE_CUSTOM_EVENT = "android.provider.calendar.action.HANDLE_CUSTOM_EVENT";
+ field public static final java.lang.String ACTION_VIEW_WORK_CALENDAR_EVENT = "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT";
field public static final java.lang.String AUTHORITY = "com.android.calendar";
field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
field public static final android.net.Uri CONTENT_URI;
@@ -35946,6 +36002,7 @@ package android.provider {
field public static final java.lang.String EXTRA_EVENT_ALL_DAY = "allDay";
field public static final java.lang.String EXTRA_EVENT_BEGIN_TIME = "beginTime";
field public static final java.lang.String EXTRA_EVENT_END_TIME = "endTime";
+ field public static final java.lang.String EXTRA_EVENT_ID = "id";
}
public static final class CalendarContract.Attendees implements android.provider.BaseColumns android.provider.CalendarContract.AttendeesColumns android.provider.CalendarContract.EventsColumns {
@@ -38476,10 +38533,13 @@ package android.provider {
}
public static final class Telephony.CarrierId implements android.provider.BaseColumns {
+ method public static android.net.Uri getPreciseCarrierIdUriForSubscriptionId(int);
method public static android.net.Uri getUriForSubscriptionId(int);
field public static final java.lang.String CARRIER_ID = "carrier_id";
field public static final java.lang.String CARRIER_NAME = "carrier_name";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String PRECISE_CARRIER_ID = "precise_carrier_id";
+ field public static final java.lang.String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name";
}
public static final class Telephony.Carriers implements android.provider.BaseColumns {
@@ -40625,13 +40685,16 @@ package android.service.carrier {
public class CarrierIdentifier implements android.os.Parcelable {
ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+ ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, int);
ctor public CarrierIdentifier(byte[], java.lang.String, java.lang.String);
method public int describeContents();
+ method public int getCarrierId();
method public java.lang.String getGid1();
method public java.lang.String getGid2();
method public java.lang.String getImsi();
method public java.lang.String getMcc();
method public java.lang.String getMnc();
+ method public int getPreciseCarrierId();
method public java.lang.String getSpn();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR;
@@ -43123,12 +43186,14 @@ package android.telecom {
field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
+ field public static final java.lang.String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED";
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
+ field public static final java.lang.String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -43316,6 +43381,7 @@ package android.telephony {
field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
field public static final java.lang.String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app";
+ field public static final java.lang.String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int";
field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int";
@@ -44185,6 +44251,7 @@ package android.telephony {
method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
method public int getCallState();
method public android.os.PersistableBundle getCarrierConfig();
+ method public int getCarrierIdFromSimMccMnc();
method public deprecated android.telephony.CellLocation getCellLocation();
method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList();
method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int);
@@ -44222,6 +44289,8 @@ package android.telephony {
method public java.lang.String getSimCountryIso();
method public java.lang.String getSimOperator();
method public java.lang.String getSimOperatorName();
+ method public int getSimPreciseCarrierId();
+ method public java.lang.CharSequence getSimPreciseCarrierIdName();
method public java.lang.String getSimSerialNumber();
method public int getSimState();
method public int getSimState(int);
@@ -44278,6 +44347,7 @@ package android.telephony {
field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
field public static final java.lang.String ACTION_SMS_APP_SERVICE = "android.telephony.action.SMS_APP_SERVICE";
field public static final java.lang.String ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED";
+ field public static final java.lang.String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED";
field public static final int APPTYPE_CSIM = 4; // 0x4
field public static final int APPTYPE_ISIM = 5; // 0x5
field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -44310,6 +44380,8 @@ package android.telephony {
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE";
+ field public static final java.lang.String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID";
+ field public static final java.lang.String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.PRECISE_CARRIER_NAME";
field public static final java.lang.String EXTRA_STATE = "state";
field public static final java.lang.String EXTRA_STATE_IDLE;
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
@@ -52504,19 +52576,19 @@ package android.view.textclassifier {
method public int describeContents();
method public android.app.Person getAuthor();
method public android.os.Bundle getExtras();
+ method public java.time.ZonedDateTime getReferenceTime();
method public java.lang.CharSequence getText();
- method public java.time.ZonedDateTime getTime();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
field public static final android.app.Person PERSON_USER_LOCAL;
+ field public static final android.app.Person PERSON_USER_REMOTE;
}
public static final class ConversationActions.Message.Builder {
- ctor public ConversationActions.Message.Builder();
+ ctor public ConversationActions.Message.Builder(android.app.Person);
method public android.view.textclassifier.ConversationActions.Message build();
- method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person);
- method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime);
method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle);
+ method public android.view.textclassifier.ConversationActions.Message.Builder setReferenceTime(java.time.ZonedDateTime);
method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence);
}
@@ -52699,6 +52771,7 @@ package android.view.textclassifier {
method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request);
method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
+ field public static final java.lang.String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
field public static final java.lang.String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
field public static final android.view.textclassifier.TextClassifier NO_OP;
diff --git a/api/system-current.txt b/api/system-current.txt
index 6c2b30f6a725..da6840c88a25 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -205,6 +205,7 @@ package android {
}
public static final class R.dimen {
+ field public static final int config_mediaMetadataBitmapMaxSize = 17104904; // 0x1050008
field public static final int config_restrictedIconSize = 17104903; // 0x1050007
}
@@ -899,6 +900,7 @@ package android.app.usage {
}
public static final class UsageEvents.Event {
+ method public int getInstanceId();
method public java.lang.String getNotificationChannelId();
field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
field public static final int NOTIFICATION_SEEN = 10; // 0xa
@@ -1007,7 +1009,7 @@ package android.content {
method public void setDetectNotResponding(long);
}
- public abstract class ContentResolver {
+ public abstract class ContentResolver implements android.content.ContentInterface {
method public android.graphics.drawable.Drawable getTypeDrawable(java.lang.String);
}
@@ -1214,6 +1216,7 @@ package android.content.pm {
method public abstract boolean arePermissionsIndividuallyControlled();
method public boolean canSuspendPackage(java.lang.String);
method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+ method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.dex.ArtManager getArtManager();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public java.lang.CharSequence getHarmfulAppWarning(java.lang.String);
@@ -1225,11 +1228,13 @@ package android.content.pm {
method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int);
method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
- method public java.lang.String getWellbeingPackageName();
method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle);
+ method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, android.os.UserHandle);
+ method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, android.os.UserHandle);
+ method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, android.os.UserHandle);
method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
@@ -3966,6 +3971,19 @@ package android.os {
field public static final java.lang.String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
}
+ public class Binder implements android.os.IBinder {
+ method public static final long clearCallingWorkSource();
+ method public static final int getCallingWorkSourceUid();
+ method public static final void restoreCallingWorkSource(long);
+ method public static final long setCallingWorkSourceUid(int);
+ method public static void setProxyTransactListener(android.os.Binder.ProxyTransactListener);
+ }
+
+ public static abstract interface Binder.ProxyTransactListener {
+ method public abstract void onTransactEnded(java.lang.Object);
+ method public abstract java.lang.Object onTransactStarted(android.os.IBinder, int);
+ }
+
public final class ConfigUpdate {
field public static final java.lang.String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
@@ -5850,6 +5868,7 @@ package android.telephony {
method public void enableVideoCalling(boolean);
method public java.lang.String getAidForAppType(int);
method public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
+ method public int getCardIdForDefaultEuicc();
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
method public java.lang.String getCdmaMdn();
@@ -5916,6 +5935,7 @@ package android.telephony {
field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
+ field public static final int INVALID_CARD_ID = -1; // 0xffffffff
field public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000L; // 0xea60L
field public static final int NETWORK_MODE_CDMA_EVDO = 4; // 0x4
field public static final int NETWORK_MODE_CDMA_NO_EVDO = 5; // 0x5
@@ -7236,6 +7256,7 @@ package android.view {
package android.view.accessibility {
public final class AccessibilityManager {
+ method public int getAccessibilityWindowId(android.os.IBinder);
method public void performAccessibilityShortcut();
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 8d8999e6a30b..627ef22a5d56 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -362,6 +362,7 @@ package android.content.pm {
method public abstract java.lang.String getPermissionControllerPackageName();
method public abstract java.lang.String getServicesSystemSharedLibraryPackageName();
method public abstract java.lang.String getSharedSystemSharedLibraryPackageName();
+ method public java.lang.String getWellbeingPackageName();
method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 7fa05be29b9d..04173b217dcb 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -424,13 +424,14 @@ void StatsService::print_cmd_help(int out) {
dprintf(out, "\n be removed from memory and disk!\n");
dprintf(out, "\n");
dprintf(out,
- "usage: adb shell cmd stats dump-report [UID] NAME [--include_current_bucket] "
- "[--proto]\n");
+ "usage: adb shell cmd stats dump-report [UID] NAME [--keep_data] "
+ "[--include_current_bucket] [--proto]\n");
dprintf(out, " Dump all metric data for a configuration.\n");
dprintf(out, " UID The uid of the configuration. It is only possible to pass\n");
dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n");
dprintf(out, " calling uid is used.\n");
dprintf(out, " NAME The name of the configuration\n");
+ dprintf(out, " --keep_data Do NOT erase the data upon dumping it.\n");
dprintf(out, " --proto Print proto binary.\n");
dprintf(out, "\n");
dprintf(out, "\n");
@@ -590,6 +591,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) {
bool good = false;
bool proto = false;
bool includeCurrentBucket = false;
+ bool eraseData = true;
int uid;
string name;
if (!std::strcmp("--proto", args[argCount-1].c_str())) {
@@ -600,6 +602,10 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) {
includeCurrentBucket = true;
argCount -= 1;
}
+ if (!std::strcmp("--keep_data", args[argCount-1].c_str())) {
+ eraseData = false;
+ argCount -= 1;
+ }
if (argCount == 2) {
// Automatically pick the UID
uid = IPCThreadState::self()->getCallingUid();
@@ -627,7 +633,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) {
if (good) {
vector<uint8_t> data;
mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), getElapsedRealtimeNs(),
- includeCurrentBucket, true /* erase_data */, ADB_DUMP, &data);
+ includeCurrentBucket, eraseData, ADB_DUMP, &data);
if (proto) {
for (size_t i = 0; i < data.size(); i ++) {
dprintf(out, "%c", data[i]);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6518c61600aa..410bd198a227 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -2394,16 +2394,23 @@ message KernelWakelock {
}
/**
- * Pulls low power state information. This includes platform and subsystem sleep state information,
- * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState as defined in
+ * Pulls low power state information. If power.stats HAL is not available, this
+ * includes platform and subsystem sleep state information,
+ * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState
+ * as defined in:
* hardware/interfaces/power/1.0/types.hal
* hardware/interfaces/power/1.1/types.hal
+ * If power.stats HAL is available, this includes PowerEntityStateResidencyResult
+ * as defined in:
+ * hardware/interfaces/power/stats/1.0/types.hal
*/
message SubsystemSleepState {
// Subsystem name
optional string subsystem_name = 1;
// For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty.
// For SubsystemLowPowerStats (hal 1.1), this is the sleep state name.
+ // For PowerEntityStateResidencyResult (hal power/stats/1.0) this is the
+ // powerEntityStateName from the corresponding PowerEntityStateInfo.
optional string subname = 2;
// The number of times it entered, or voted for entering the sleep state
optional uint64 count = 3;
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
index 4501b64ad47e..c8c392016e52 100644
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
+++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
@@ -19,6 +19,8 @@
#include <android/hardware/power/1.0/IPower.h>
#include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/stats/1.0/IPowerStats.h>
+
#include <fcntl.h>
#include <hardware/power.h>
#include <hardware_legacy/power.h>
@@ -42,9 +44,12 @@ using android::hardware::hidl_vec;
using android::hardware::power::V1_0::IPower;
using android::hardware::power::V1_0::PowerStatePlatformSleepState;
using android::hardware::power::V1_0::PowerStateVoter;
-using android::hardware::power::V1_0::Status;
using android::hardware::power::V1_1::PowerStateSubsystem;
using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
+using android::hardware::power::stats::V1_0::PowerEntityInfo;
+using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
+using android::hardware::power::stats::V1_0::PowerEntityStateSpace;
+
using android::hardware::Return;
using android::hardware::Void;
@@ -55,44 +60,209 @@ namespace android {
namespace os {
namespace statsd {
+std::function<bool(vector<shared_ptr<LogEvent>>* data)> gPuller = {};
+
sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr;
sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr;
+sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr;
+
+std::unordered_map<uint32_t, std::string> gEntityNames = {};
+std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> gStateNames = {};
+
std::mutex gPowerHalMutex;
-bool gPowerHalExists = true;
-bool getPowerHal() {
- if (gPowerHalExists && gPowerHalV1_0 == nullptr) {
- gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
- if (gPowerHalV1_0 != nullptr) {
- gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
- ALOGI("Loaded power HAL service");
- } else {
- ALOGW("Couldn't load power HAL service");
- gPowerHalExists = false;
+// The caller must be holding gPowerHalMutex.
+void deinitPowerStatsLocked() {
+ gPowerHalV1_0 = nullptr;
+ gPowerHalV1_1 = nullptr;
+ gPowerStatsHalV1_0 = nullptr;
+}
+
+struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient {
+ virtual void serviceDied(uint64_t cookie,
+ const wp<android::hidl::base::V1_0::IBase>& who) override {
+ // The HAL just died. Reset all handles to HAL services.
+ std::lock_guard<std::mutex> lock(gPowerHalMutex);
+ deinitPowerStatsLocked();
+ }
+};
+
+sp<PowerHalDeathRecipient> gDeathRecipient = new PowerHalDeathRecipient();
+
+SubsystemSleepStatePuller::SubsystemSleepStatePuller() :
+ StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) {
+}
+
+// The caller must be holding gPowerHalMutex.
+bool checkResultLocked(const Return<void> &ret, const char* function) {
+ if (!ret.isOk()) {
+ ALOGE("%s failed: requested HAL service not available. Description: %s",
+ function, ret.description().c_str());
+ if (ret.isDeadObject()) {
+ deinitPowerStatsLocked();
+ }
+ return false;
+ }
+ return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+// gPowerStatsHalV1_0 must not be null
+bool initializePowerStats() {
+ using android::hardware::power::stats::V1_0::Status;
+
+ // Clear out previous content if we are re-initializing
+ gEntityNames.clear();
+ gStateNames.clear();
+
+ Return<void> ret;
+ ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
+ if (status != Status::SUCCESS) {
+ ALOGE("Error getting power entity info");
+ return;
+ }
+
+ // construct lookup table of powerEntityId to power entity name
+ for (auto info : infos) {
+ gEntityNames.emplace(info.powerEntityId, info.powerEntityName);
}
+ });
+ if (!checkResultLocked(ret, __func__)) {
+ return false;
}
- return gPowerHalV1_0 != nullptr;
+
+ ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
+ if (status != Status::SUCCESS) {
+ ALOGE("Error getting state info");
+ return;
+ }
+
+ // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
+ for (auto stateSpace : stateSpaces) {
+ std::unordered_map<uint32_t, std::string> stateNames = {};
+ for (auto state : stateSpace.states) {
+ stateNames.emplace(state.powerEntityStateId,
+ state.powerEntityStateName);
+ }
+ gStateNames.emplace(stateSpace.powerEntityId, stateNames);
+ }
+ });
+ if (!checkResultLocked(ret, __func__)) {
+ return false;
+ }
+
+ return (!gEntityNames.empty()) && (!gStateNames.empty());
}
-SubsystemSleepStatePuller::SubsystemSleepStatePuller() : StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) {
+// The caller must be holding gPowerHalMutex.
+bool getPowerStatsHalLocked() {
+ if(gPowerStatsHalV1_0 == nullptr) {
+ gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService();
+ if (gPowerStatsHalV1_0 == nullptr) {
+ ALOGE("Unable to get power.stats HAL service.");
+ return false;
+ }
+
+ // Link death recipient to power.stats service handle
+ hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0);
+ if (!linked.isOk()) {
+ ALOGE("Transaction error in linking to power.stats HAL death: %s",
+ linked.description().c_str());
+ deinitPowerStatsLocked();
+ return false;
+ } else if (!linked) {
+ ALOGW("Unable to link to power.stats HAL death notifications");
+ // We should still continue even though linking failed
+ }
+ return initializePowerStats();
+ }
+ return true;
}
-bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
- std::lock_guard<std::mutex> lock(gPowerHalMutex);
+// The caller must be holding gPowerHalMutex.
+bool getIPowerStatsDataLocked(vector<shared_ptr<LogEvent>>* data) {
+ using android::hardware::power::stats::V1_0::Status;
- if (!getPowerHal()) {
- ALOGE("Power Hal not loaded");
+ if(!getPowerStatsHalLocked()) {
return false;
}
int64_t wallClockTimestampNs = getWallClockNs();
int64_t elapsedTimestampNs = getElapsedRealtimeNs();
- data->clear();
+ // Get power entity state residency data
+ bool success = false;
+ Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
+ [&data, &success, wallClockTimestampNs, elapsedTimestampNs]
+ (auto results, auto status) {
+ if (status == Status::NOT_SUPPORTED) {
+ ALOGW("getPowerEntityStateResidencyData is not supported");
+ success = false;
+ return;
+ }
+
+ for(auto result : results) {
+ for(auto stateResidency : result.stateResidencyData) {
+ auto statePtr = make_shared<LogEvent>(
+ android::util::SUBSYSTEM_SLEEP_STATE,
+ wallClockTimestampNs, elapsedTimestampNs);
+ statePtr->write(gEntityNames.at(result.powerEntityId));
+ statePtr->write(gStateNames.at(result.powerEntityId)
+ .at(stateResidency.powerEntityStateId));
+ statePtr->write(stateResidency.totalStateEntryCount);
+ statePtr->write(stateResidency.totalTimeInStateMs);
+ statePtr->init();
+ data->emplace_back(statePtr);
+ }
+ }
+ success = true;
+ });
+ // Intentionally not returning early here.
+ // bool success determines if this succeeded or not.
+ checkResultLocked(ret, __func__);
- Return<void> ret;
+ return success;
+}
+
+// The caller must be holding gPowerHalMutex.
+bool getPowerHalLocked() {
+ if(gPowerHalV1_0 == nullptr) {
+ gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
+ if(gPowerHalV1_0 == nullptr) {
+ ALOGE("Unable to get power HAL service.");
+ return false;
+ }
+ gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
+
+ // Link death recipient to power service handle
+ hardware::Return<bool> linked = gPowerHalV1_0->linkToDeath(gDeathRecipient, 0);
+ if (!linked.isOk()) {
+ ALOGE("Transaction error in linking to power HAL death: %s",
+ linked.description().c_str());
+ gPowerHalV1_0 = nullptr;
+ return false;
+ } else if (!linked) {
+ ALOGW("Unable to link to power. death notifications");
+ // We should still continue even though linking failed
+ }
+ }
+ return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) {
+ using android::hardware::power::V1_0::Status;
+
+ if(!getPowerHalLocked()) {
+ return false;
+ }
+
+ int64_t wallClockTimestampNs = getWallClockNs();
+ int64_t elapsedTimestampNs = getElapsedRealtimeNs();
+ Return<void> ret;
ret = gPowerHalV1_0->getPlatformLowPowerStats(
- [&data, wallClockTimestampNs, elapsedTimestampNs](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
+ [&data, wallClockTimestampNs, elapsedTimestampNs]
+ (hidl_vec<PowerStatePlatformSleepState> states, Status status) {
if (status != Status::SUCCESS) return;
for (size_t i = 0; i < states.size(); i++) {
@@ -128,9 +298,7 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data)
}
}
});
- if (!ret.isOk()) {
- ALOGE("getLowPowerStats() failed: power HAL service not available");
- gPowerHalV1_0 = nullptr;
+ if (!checkResultLocked(ret, __func__)) {
return false;
}
@@ -139,35 +307,68 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data)
android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
if (gPowerHal_1_1 != nullptr) {
ret = gPowerHal_1_1->getSubsystemLowPowerStats(
- [&data, wallClockTimestampNs, elapsedTimestampNs](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
- if (status != Status::SUCCESS) return;
-
- if (subsystems.size() > 0) {
- for (size_t i = 0; i < subsystems.size(); i++) {
- const PowerStateSubsystem& subsystem = subsystems[i];
- for (size_t j = 0; j < subsystem.states.size(); j++) {
- const PowerStateSubsystemSleepState& state =
- subsystem.states[j];
- auto subsystemStatePtr = make_shared<LogEvent>(
- android::util::SUBSYSTEM_SLEEP_STATE,
- wallClockTimestampNs, elapsedTimestampNs);
- subsystemStatePtr->write(subsystem.name);
- subsystemStatePtr->write(state.name);
- subsystemStatePtr->write(state.totalTransitions);
- subsystemStatePtr->write(state.residencyInMsecSinceBoot);
- subsystemStatePtr->init();
- data->push_back(subsystemStatePtr);
- VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
- subsystem.name.c_str(), state.name.c_str(),
- (long long)state.residencyInMsecSinceBoot,
- (long long)state.totalTransitions,
- (long long)state.lastEntryTimestampMs);
- }
- }
+ [&data, wallClockTimestampNs, elapsedTimestampNs]
+ (hidl_vec<PowerStateSubsystem> subsystems, Status status) {
+ if (status != Status::SUCCESS) return;
+
+ if (subsystems.size() > 0) {
+ for (size_t i = 0; i < subsystems.size(); i++) {
+ const PowerStateSubsystem& subsystem = subsystems[i];
+ for (size_t j = 0; j < subsystem.states.size(); j++) {
+ const PowerStateSubsystemSleepState& state =
+ subsystem.states[j];
+ auto subsystemStatePtr = make_shared<LogEvent>(
+ android::util::SUBSYSTEM_SLEEP_STATE,
+ wallClockTimestampNs, elapsedTimestampNs);
+ subsystemStatePtr->write(subsystem.name);
+ subsystemStatePtr->write(state.name);
+ subsystemStatePtr->write(state.totalTransitions);
+ subsystemStatePtr->write(state.residencyInMsecSinceBoot);
+ subsystemStatePtr->init();
+ data->push_back(subsystemStatePtr);
+ VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
+ subsystem.name.c_str(), state.name.c_str(),
+ (long long)state.residencyInMsecSinceBoot,
+ (long long)state.totalTransitions,
+ (long long)state.lastEntryTimestampMs);
}
- });
+ }
+ }
+ });
}
- return true;
+ return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() {
+ std::function<bool(vector<shared_ptr<LogEvent>>* data)> ret = {};
+
+ // First see if power.stats HAL is available. Fall back to power HAL if
+ // power.stats HAL is unavailable.
+ if(android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) {
+ ALOGI("Using power.stats HAL");
+ ret = getIPowerStatsDataLocked;
+ } else if(android::hardware::power::V1_0::IPower::getService() != nullptr) {
+ ALOGI("Using power HAL");
+ ret = getIPowerDataLocked;
+ }
+
+ return ret;
+}
+
+bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
+ std::lock_guard<std::mutex> lock(gPowerHalMutex);
+
+ if(!gPuller) {
+ gPuller = getPullerLocked();
+ }
+
+ if(gPuller) {
+ return gPuller(data);
+ }
+
+ ALOGE("Unable to load Power Hal or power.stats HAL");
+ return false;
}
} // namespace statsd
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 355df2986a0b..237f8b902015 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -240,6 +240,49 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) {
EXPECT_EQ(2, report.annotation(0).field_int32());
}
+TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) {
+ // Setup a simple config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = wakelockAcquireMatcher;
+
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(123456);
+ countMetric->set_what(wakelockAcquireMatcher.id());
+ countMetric->set_bucket(FIVE_MINUTES);
+
+ ConfigKey cfgKey;
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey);
+
+ std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
+ auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 2);
+ processor->OnLogEvent(event.get());
+
+ vector<uint8_t> bytes;
+ ConfigMetricsReportList output;
+
+ // Dump report WITHOUT erasing data.
+ processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, &bytes);
+ output.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_EQ(output.reports_size(), 1);
+ EXPECT_EQ(output.reports(0).metrics_size(), 1);
+ EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
+
+ // Dump report WITH erasing data. There should be data since we didn't previously erase it.
+ processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, &bytes);
+ output.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_EQ(output.reports_size(), 1);
+ EXPECT_EQ(output.reports(0).metrics_size(), 1);
+ EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
+
+ // Dump report again. There should be no data since we erased it.
+ processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, &bytes);
+ output.ParseFromArray(bytes.data(), bytes.size());
+ bool noData = (output.reports_size() == 0) || (output.reports(0).metrics_size() == 0);
+ EXPECT_TRUE(noData);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tools/localtools/Android.bp b/cmds/statsd/tools/localtools/Android.bp
new file mode 100644
index 000000000000..75a57a3f3068
--- /dev/null
+++ b/cmds/statsd/tools/localtools/Android.bp
@@ -0,0 +1,25 @@
+java_binary_host {
+ name: "statsd_localdrive",
+ manifest: "localdrive_manifest.txt",
+ srcs: [
+ "src/com/android/statsd/shelltools/localdrive/*.java",
+ "src/com/android/statsd/shelltools/Utils.java",
+ ],
+ static_libs: [
+ "platformprotos",
+ "guava",
+ ],
+}
+
+java_binary_host {
+ name: "statsd_testdrive",
+ manifest: "testdrive_manifest.txt",
+ srcs: [
+ "src/com/android/statsd/shelltools/testdrive/*.java",
+ "src/com/android/statsd/shelltools/Utils.java",
+ ],
+ static_libs: [
+ "platformprotos",
+ "guava",
+ ],
+} \ No newline at end of file
diff --git a/cmds/statsd/tools/localtools/localdrive_manifest.txt b/cmds/statsd/tools/localtools/localdrive_manifest.txt
new file mode 100644
index 000000000000..035cea1134bc
--- /dev/null
+++ b/cmds/statsd/tools/localtools/localdrive_manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.statsd.shelltools.localdrive.LocalDrive
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
new file mode 100644
index 000000000000..597377e34ac3
--- /dev/null
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.statsd.shelltools;
+
+import com.android.os.StatsLog.ConfigMetricsReportList;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * Utilities for local use of statsd.
+ */
+public class Utils {
+
+ public static final String CMD_UPDATE_CONFIG = "cmd stats config update";
+ public static final String CMD_DUMP_REPORT = "cmd stats dump-report";
+ public static final String CMD_REMOVE_CONFIG = "cmd stats config remove";
+
+ public static final String SHELL_UID = "2000"; // Use shell, even if rooted.
+
+ /**
+ * Runs adb shell command with output directed to outputFile if non-null.
+ */
+ public static void runCommand(File outputFile, Logger logger, String... commands)
+ throws IOException, InterruptedException {
+ ProcessBuilder pb = new ProcessBuilder(commands);
+ if (outputFile != null && outputFile.exists() && outputFile.canWrite()) {
+ pb.redirectOutput(outputFile);
+ }
+ Process process = pb.start();
+
+ // Capture any errors
+ StringBuilder err = new StringBuilder();
+ BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+ for (String line = br.readLine(); line != null; line = br.readLine()) {
+ err.append(line).append('\n');
+ }
+ logger.severe(err.toString());
+
+ // Check result
+ if (process.waitFor() == 0) {
+ logger.fine("Adb command successful.");
+ } else {
+ logger.severe("Abnormal adb shell cmd termination for: " + String.join(",", commands));
+ throw new RuntimeException("Error running adb command: " + err.toString());
+ }
+ }
+
+ /**
+ * Dumps the report from the device and converts it to a ConfigMetricsReportList.
+ * Erases the data if clearData is true.
+ */
+ public static ConfigMetricsReportList getReportList(long configId, boolean clearData,
+ Logger logger) throws IOException, InterruptedException {
+ try {
+ File outputFile = File.createTempFile("statsdret", ".bin");
+ outputFile.deleteOnExit();
+ runCommand(
+ outputFile,
+ logger,
+ "adb",
+ "shell",
+ CMD_DUMP_REPORT,
+ SHELL_UID,
+ String.valueOf(configId),
+ clearData ? "" : "--keep_data",
+ "--include_current_bucket",
+ "--proto");
+ ConfigMetricsReportList reportList =
+ ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
+ return reportList;
+ } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+ logger.severe("Failed to fetch and parse the statsd output report. "
+ + "Perhaps there is not a valid statsd config for the requested "
+ + "uid=" + SHELL_UID
+ + ", configId=" + configId
+ + ".");
+ throw (e);
+ }
+ }
+
+ public static void setUpLogger(Logger logger, boolean debug) {
+ ConsoleHandler handler = new ConsoleHandler();
+ handler.setFormatter(new LocalToolsFormatter());
+ logger.setUseParentHandlers(false);
+ if (debug) {
+ handler.setLevel(Level.ALL);
+ logger.setLevel(Level.ALL);
+ }
+ logger.addHandler(handler);
+ }
+
+ public static class LocalToolsFormatter extends Formatter {
+ public String format(LogRecord record) {
+ return record.getMessage() + "\n";
+ }
+ }
+}
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
new file mode 100644
index 000000000000..08074ede9d31
--- /dev/null
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.statsd.shelltools.localdrive;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.statsd.shelltools.Utils;
+
+import com.google.common.io.Files;
+import com.google.protobuf.TextFormat;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * Tool for using statsd locally. Can upload a config and get the data. Handles
+ * both binary and human-readable protos.
+ * To make: make statsd_localdrive
+ * To run: statsd_localdrive (i.e. ./out/host/linux-x86/bin/statsd_localdrive)
+ */
+public class LocalDrive {
+ private static final boolean DEBUG = false;
+
+ public static final long DEFAULT_CONFIG_ID = 56789;
+
+ public static final String BINARY_FLAG = "--binary";
+ public static final String CLEAR_DATA = "--clear";
+ public static final String NO_UID_MAP_FLAG = "--no-uid-map";
+
+ public static final String HELP_STRING =
+ "Usage:\n\n" +
+
+ "statsd_local upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+ " Uploads the given statsd config file (in binary or human-readable-text format).\n" +
+ " If a config with this id already exists, removes it first.\n" +
+ " CONFIG_FILE Location of config file on host.\n" +
+ " CONFIG_ID Long ID to associate with this config. If absent, uses "
+ + DEFAULT_CONFIG_ID + ".\n" +
+ " --binary Config is in binary format; otherwise, assumed human-readable text.\n" +
+ // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
+ "\n" +
+
+ "statsd_local update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+ " Same as upload, but does not remove the old config first (if it already exists).\n" +
+ // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
+ "\n" +
+
+ "statsd_local get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
+ " Prints the output statslog data (in binary or human-readable-text format).\n" +
+ " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
+ " --binary Output should be in binary, instead of default human-readable text.\n" +
+ " Binary output can be redirected as usual (e.g. > FILENAME).\n" +
+ " --no-uid-map Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" +
+ " --clear Erase the data from statsd afterwards. Does not remove the config.\n" +
+ // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data]
+ // --include_current_bucket --proto
+ "\n" +
+
+ "statsd_local remove [CONFIG_ID]\n" +
+ " Removes the config.\n" +
+ " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
+ // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
+ "\n" +
+
+ "statsd_local clear [CONFIG_ID]\n" +
+ " Clears the data associated with the config.\n" +
+ " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
+ // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
+ // --include_current_bucket --proto
+ "";
+
+
+ private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName());
+
+ /** Usage: make statsd_localdrive && statsd_localdrive */
+ public static void main(String[] args) {
+ Utils.setUpLogger(sLogger, DEBUG);
+
+ if (args.length > 0) {
+ switch (args[0]) {
+ case "clear":
+ cmdClear(args);
+ return;
+ case "get-data":
+ cmdGetData(args);
+ return;
+ case "remove":
+ cmdRemove(args);
+ return;
+ case "update":
+ cmdUpdate(args);
+ return;
+ case "upload":
+ cmdUpload(args);
+ return;
+ }
+ }
+ printHelp();
+ }
+
+ private static void printHelp() {
+ sLogger.info(HELP_STRING);
+ }
+
+ // upload CONFIG_FILE [CONFIG_ID] [--binary]
+ private static boolean cmdUpload(String[] args) {
+ return updateConfig(args, true);
+ }
+
+ // update CONFIG_FILE [CONFIG_ID] [--binary]
+ private static boolean cmdUpdate(String[] args) {
+ return updateConfig(args, false);
+ }
+
+ private static boolean updateConfig(String[] args, boolean removeOldConfig) {
+ int argCount = args.length - 1; // Used up one for upload/update.
+
+ // Get CONFIG_FILE
+ if (argCount < 1) {
+ sLogger.severe("No config file provided.");
+ printHelp();
+ return false;
+ }
+ final String origConfigLocation = args[1];
+ if (!new File(origConfigLocation).exists()) {
+ sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
+ return false;
+ }
+ argCount--;
+
+ // Get --binary
+ boolean binary = contains(args, 2, BINARY_FLAG);
+ if (binary) argCount --;
+
+ // Get CONFIG_ID
+ long configId;
+ try {
+ configId = getConfigId(argCount < 1, args, 2);
+ } catch (NumberFormatException e) {
+ sLogger.severe("Invalid config id provided.");
+ printHelp();
+ return false;
+ }
+ sLogger.fine(String.format("updateConfig with %s %d %b %b",
+ origConfigLocation, configId, binary, removeOldConfig));
+
+ // Remove the old config.
+ if (removeOldConfig) {
+ try {
+ Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
+ Utils.SHELL_UID, String.valueOf(configId));
+ Utils.getReportList(configId, true /* clearData */, sLogger);
+ } catch (InterruptedException | IOException e) {
+ sLogger.severe("Failed to remove config: " + e.getMessage());
+ return false;
+ }
+ }
+
+ // Upload the config.
+ String configLocation;
+ if (binary) {
+ configLocation = origConfigLocation;
+ } else {
+ StatsdConfig.Builder builder = StatsdConfig.newBuilder();
+ try {
+ TextFormat.merge(new FileReader(origConfigLocation), builder);
+ } catch (IOException e) {
+ sLogger.severe("Failed to read config file " + origConfigLocation + ": "
+ + e.getMessage());
+ return false;
+ }
+
+ try {
+ File tempConfigFile = File.createTempFile("statsdconfig", ".config");
+ tempConfigFile.deleteOnExit();
+ Files.write(builder.build().toByteArray(), tempConfigFile);
+ configLocation = tempConfigFile.getAbsolutePath();
+ } catch (IOException e) {
+ sLogger.severe("Failed to write temp config file: " + e.getMessage());
+ return false;
+ }
+ }
+ String remotePath = "/data/local/tmp/statsdconfig.config";
+ try {
+ Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath);
+ Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|",
+ Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId));
+ } catch (InterruptedException | IOException e) {
+ sLogger.severe("Failed to update config: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
+ private static boolean cmdGetData(String[] args) {
+ boolean binary = contains(args, 1, BINARY_FLAG);
+ boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG);
+ boolean clearData = contains(args, 1, CLEAR_DATA);
+
+ // Get CONFIG_ID
+ int argCount = args.length - 1; // Used up one for get-data.
+ if (binary) argCount--;
+ if (noUidMap) argCount--;
+ if (clearData) argCount--;
+ long configId;
+ try {
+ configId = getConfigId(argCount < 1, args, 1);
+ } catch (NumberFormatException e) {
+ sLogger.severe("Invalid config id provided.");
+ printHelp();
+ return false;
+ }
+ sLogger.fine(String.format("cmdGetData with %d %b %b %b",
+ configId, clearData, binary, noUidMap));
+
+ // Get the StatsLog
+ // Even if the args request no modifications, we still parse it to make sure it's valid.
+ ConfigMetricsReportList reportList;
+ try {
+ reportList = Utils.getReportList(configId, clearData, sLogger);
+ } catch (IOException | InterruptedException e) {
+ sLogger.severe("Failed to get report list: " + e.getMessage());
+ return false;
+ }
+ if (noUidMap) {
+ ConfigMetricsReportList.Builder builder
+ = ConfigMetricsReportList.newBuilder(reportList);
+ // Clear the reports, then add them back without their UidMap.
+ builder.clearReports();
+ for (ConfigMetricsReport report : reportList.getReportsList()) {
+ builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap());
+ }
+ reportList = builder.build();
+ }
+
+ if (!binary) {
+ sLogger.info(reportList.toString());
+ } else {
+ try {
+ System.out.write(reportList.toByteArray());
+ } catch (IOException e) {
+ sLogger.severe("Failed to output binary statslog proto: "
+ + e.getMessage());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // clear [CONFIG_ID]
+ private static boolean cmdClear(String[] args) {
+ // Get CONFIG_ID
+ long configId;
+ try {
+ configId = getConfigId(false, args, 1);
+ } catch (NumberFormatException e) {
+ sLogger.severe("Invalid config id provided.");
+ printHelp();
+ return false;
+ }
+ sLogger.fine(String.format("cmdClear with %d", configId));
+
+ try {
+ Utils.getReportList(configId, true /* clearData */, sLogger);
+ } catch (IOException | InterruptedException e) {
+ sLogger.severe("Failed to get report list: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ // remove [CONFIG_ID]
+ private static boolean cmdRemove(String[] args) {
+ // Get CONFIG_ID
+ long configId;
+ try {
+ configId = getConfigId(false, args, 1);
+ } catch (NumberFormatException e) {
+ sLogger.severe("Invalid config id provided.");
+ printHelp();
+ return false;
+ }
+ sLogger.fine(String.format("cmdRemove with %d", configId));
+
+ try {
+ Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
+ Utils.SHELL_UID, String.valueOf(configId));
+ } catch (InterruptedException | IOException e) {
+ sLogger.severe("Failed to remove config: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Searches through the array to see if it contains (precisely) the given value, starting
+ * at the given firstIdx.
+ */
+ private static boolean contains(String[] array, int firstIdx, String value) {
+ if (value == null) return false;
+ if (firstIdx < 0) return false;
+ for (int i = firstIdx; i < array.length; i++) {
+ if (value.equals(array[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist.
+ * If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead.
+ */
+ private static long getConfigId(boolean justUseDefault, String[] args, int idx)
+ throws NumberFormatException {
+ if (justUseDefault || args.length <= idx || idx < 0) {
+ return DEFAULT_CONFIG_ID;
+ }
+ try {
+ return Long.valueOf(args[idx]);
+ } catch (NumberFormatException e) {
+ sLogger.severe("Bad config id provided: " + args[idx]);
+ throw e;
+ }
+ }
+}
diff --git a/cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index cc4e386bfdf0..f7bd44aeab62 100644
--- a/cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.statsd.testdrive;
+package com.android.statsd.shelltools.testdrive;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
@@ -21,20 +21,15 @@ import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.AtomsProto.Atom;
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.statsd.shelltools.Utils;
import com.google.common.io.Files;
import com.google.protobuf.TextFormat;
import com.google.protobuf.TextFormat.ParseException;
-import java.io.BufferedReader;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.logging.ConsoleHandler;
-import java.util.logging.Formatter;
import java.util.logging.Level;
-import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class TestDrive {
@@ -42,10 +37,6 @@ public class TestDrive {
public static final int PULL_ATOM_START = 10000;
public static final long ATOM_MATCHER_ID = 1234567;
- public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
- public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
- public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
- public static final String CONFIG_UID = "2000"; // shell uid
public static final long CONFIG_ID = 54321;
private static boolean mIsPushedAtom = false;
@@ -53,6 +44,9 @@ public class TestDrive {
private static final Logger logger = Logger.getLogger(TestDrive.class.getName());
public static void main(String[] args) {
+ TestDrive testDrive = new TestDrive();
+ Utils.setUpLogger(logger, false);
+
if (args.length != 1) {
logger.log(Level.SEVERE, "Usage: ./test_drive <atomId>");
return;
@@ -70,12 +64,6 @@ public class TestDrive {
}
mIsPushedAtom = atomId < PULL_ATOM_START;
- TestDrive testDrive = new TestDrive();
- TestDriveFormatter formatter = new TestDriveFormatter();
- ConsoleHandler handler = new ConsoleHandler();
- handler.setFormatter(formatter);
- logger.addHandler(handler);
- logger.setUseParentHandlers(false);
try {
StatsdConfig config = testDrive.createConfig(atomId);
@@ -109,55 +97,21 @@ public class TestDrive {
configFile.deleteOnExit();
Files.write(config.toByteArray(), configFile);
String remotePath = "/data/local/tmp/" + configFile.getName();
- runCommand(null, "adb", "push", configFile.getAbsolutePath(), remotePath);
- runCommand(
- null, "adb", "shell", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+ Utils.runCommand(null, logger, "adb", "push", configFile.getAbsolutePath(), remotePath);
+ Utils.runCommand(null, logger,
+ "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
String.valueOf(CONFIG_ID));
}
private void removeConfig() {
try {
- runCommand(null, "adb", "shell", REMOVE_CONFIG_CMD, String.valueOf(CONFIG_ID));
+ Utils.runCommand(null, logger,
+ "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to remove config: " + e.getMessage());
}
}
- // Runs a shell command. Output should go to outputFile. Returns error string.
- private String runCommand(File outputFile, String... commands)
- throws IOException, InterruptedException {
- // Run macro on target
- ProcessBuilder pb = new ProcessBuilder(commands);
- // pb.redirectErrorStream(true);
-
- if (outputFile != null && outputFile.exists() && outputFile.canWrite()) {
- pb.redirectOutput(outputFile);
- }
- Process process = pb.start();
-
- // capture any errors
- StringBuilder out = new StringBuilder();
- // Read output
- BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
- String line = null, previous = null;
- while ((line = br.readLine()) != null) {
- if (!line.equals(previous)) {
- previous = line;
- out.append(line).append('\n');
- logger.fine(line);
- }
- }
-
- // Check result
- if (process.waitFor() == 0) {
- logger.fine("Success!");
- } else {
- // Abnormal termination: Log command parameters and output and throw ExecutionException
- logger.log(Level.SEVERE, out.toString());
- }
- return out.toString();
- }
-
private StatsdConfig createConfig(int atomId) {
try {
if (mIsPushedAtom) {
@@ -210,37 +164,8 @@ public class TestDrive {
return builder;
}
- private ConfigMetricsReportList getReportList() throws Exception {
- try {
- File outputFile = File.createTempFile("statsdret", ".bin");
- outputFile.deleteOnExit();
- runCommand(
- outputFile,
- "adb",
- "shell",
- DUMP_REPORT_CMD,
- String.valueOf(CONFIG_ID),
- "--include_current_bucket",
- "--proto");
- ConfigMetricsReportList reportList =
- ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
- return reportList;
- } catch (com.google.protobuf.InvalidProtocolBufferException e) {
- logger.log(
- Level.SEVERE,
- "Failed to fetch and parse the statsd output report. "
- + "Perhaps there is not a valid statsd config for the requested "
- + "uid="
- + CONFIG_UID
- + ", id="
- + CONFIG_ID
- + ".");
- throw (e);
- }
- }
-
private void dumpMetrics() throws Exception {
- ConfigMetricsReportList reportList = getReportList();
+ ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, logger);
// We may get multiple reports. Take the last one.
ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
// Really should be only one metric.
@@ -294,9 +219,4 @@ public class TestDrive {
+ "\n"
+ "hash_strings_in_metric_report: false";
- public static class TestDriveFormatter extends Formatter {
- public String format(LogRecord record) {
- return record.getMessage() + "\n";
- }
- }
}
diff --git a/cmds/statsd/tools/localtools/testdrive_manifest.txt b/cmds/statsd/tools/localtools/testdrive_manifest.txt
new file mode 100644
index 000000000000..625ebfa4312a
--- /dev/null
+++ b/cmds/statsd/tools/localtools/testdrive_manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.statsd.shelltools.testdrive.TestDrive
diff --git a/cmds/statsd/tools/statsd-testdrive/Android.bp b/cmds/statsd/tools/statsd-testdrive/Android.bp
deleted file mode 100644
index f566bc7f2a53..000000000000
--- a/cmds/statsd/tools/statsd-testdrive/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-java_binary_host {
- name: "statsd_testdrive",
- manifest: "manifest.txt",
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "platformprotos",
- "guava",
- ],
-}
diff --git a/cmds/statsd/tools/statsd-testdrive/manifest.txt b/cmds/statsd/tools/statsd-testdrive/manifest.txt
deleted file mode 100644
index 0266d1143245..000000000000
--- a/cmds/statsd/tools/statsd-testdrive/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-class: com.android.statsd.testdrive.TestDrive
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index bacb991012fd..458c7decab5a 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -2861,7 +2861,6 @@ Lcom/android/internal/telephony/dataconnection/DataConnection;->mActivatingState
Lcom/android/internal/telephony/dataconnection/DataConnection;->mActiveState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcActiveState;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mConnectionParams:Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mDataRegState:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDcFailCause:Lcom/android/internal/telephony/dataconnection/DcFailCause;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingErrorCreatingConnection:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectionErrorCreatingConnection;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectingState;
@@ -2872,10 +2871,8 @@ Lcom/android/internal/telephony/dataconnection/DataConnection;->mLinkProperties:
Lcom/android/internal/telephony/dataconnection/DataConnection;->mNetworkInfo:Landroid/net/NetworkInfo;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mPhone:Lcom/android/internal/telephony/Phone;
Lcom/android/internal/telephony/dataconnection/DataConnection;->mRilRat:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DcFailCause;)V
Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfConnected(Ljava/lang/String;)V
Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfDisconnectDcRetrying(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyConnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;Lcom/android/internal/telephony/dataconnection/DcFailCause;Z)V
Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;Z)V
Lcom/android/internal/telephony/dataconnection/DataConnection;->onConnect(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;)V
Lcom/android/internal/telephony/dataconnection/DataConnection;->tearDownData(Ljava/lang/Object;)V
@@ -2884,22 +2881,6 @@ Lcom/android/internal/telephony/dataconnection/DcController;->lr(Ljava/lang/Stri
Lcom/android/internal/telephony/dataconnection/DcController;->mDcListActiveByCid:Ljava/util/HashMap;
Lcom/android/internal/telephony/dataconnection/DcController;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker;
Lcom/android/internal/telephony/dataconnection/DcController;->mDcTesterDeactivateAll:Lcom/android/internal/telephony/dataconnection/DcTesterDeactivateAll;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ACTIVATION_REJECT_GGSN:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ACTIVATION_REJECT_UNSPECIFIED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->APN_TYPE_CONFLICT:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->INSUFFICIENT_RESOURCES:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->MISSING_UNKNOWN_APN:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->NSAPI_IN_USE:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_IPV4_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_IPV6_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_SINGLE_BEARER_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->OPERATOR_BARRED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->PROTOCOL_ERRORS:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_NOT_SUBSCRIBED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_NOT_SUPPORTED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_OUT_OF_ORDER:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->UNKNOWN_PDP_ADDRESS_TYPE:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->USER_AUTHENTICATION:Lcom/android/internal/telephony/dataconnection/DcFailCause;
Lcom/android/internal/telephony/dataconnection/DcTracker$RecoveryAction;->isAggressiveRecovery(I)Z
Lcom/android/internal/telephony/dataconnection/DcTracker;->cancelReconnectAlarm(Lcom/android/internal/telephony/dataconnection/ApnContext;)V
Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpAllConnections(Ljava/lang/String;)V
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5e445d14a08b..b584d5d4c3b4 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2545,7 +2545,7 @@ public class Activity extends ContextThemeWrapper
* picture-in-picture.
*
* @return true if the system successfully put this activity into picture-in-picture mode or was
- * already in picture-in-picture mode (@see {@link #isInPictureInPictureMode()). If the device
+ * already in picture-in-picture mode (see {@link #isInPictureInPictureMode()}). If the device
* does not support picture-in-picture, return false.
*/
public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index af3da0cbf5ee..b42d53ad10f6 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -196,8 +196,26 @@ public abstract class ActivityManagerInternal {
public abstract void updateOomAdj();
public abstract void updateCpuStats();
- public abstract void updateUsageStats(
+
+ /**
+ * Update battery stats on activity usage.
+ * @param activity
+ * @param uid
+ * @param userId
+ * @param started
+ */
+ public abstract void updateBatteryStats(
ComponentName activity, int uid, int userId, boolean resumed);
+
+ /**
+ * Update UsageStats of the activity.
+ * @param activity
+ * @param userId
+ * @param event
+ * @param appToken ActivityRecord's appToken.
+ */
+ public abstract void updateActivityUsageStats(
+ ComponentName activity, int userId, int event, IBinder appToken);
public abstract void updateForegroundTimeIfOnBattery(
String packageName, int uid, long cpuTimeDiff);
public abstract void sendForegroundProfileChanged(int userId);
@@ -288,6 +306,6 @@ public abstract class ActivityManagerInternal {
public abstract void setDebugFlagsForStartingActivity(ActivityInfo aInfo, int startFlags,
ProfilerInfo profilerInfo, Object wmLock);
- /** Checks if process running with given pid has access to full external storage or not */
- public abstract boolean isAppStorageSandboxed(int pid, int uid);
+ /** Returns mount mode for process running with given pid */
+ public abstract int getStorageMountMode(int pid, int uid);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 67d9ad6e93c6..2b81c86e1b0d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -44,6 +44,7 @@ import android.content.pm.InstantAppInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
@@ -791,6 +792,16 @@ public class ApplicationPackageManager extends PackageManager {
throw new NameNotFoundException("No shared userid for user:"+sharedUserName);
}
+ @Override
+ public List<ModuleInfo> getInstalledModules(int flags) {
+ return null;
+ }
+
+ @Override
+ public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException {
+ return null;
+ }
+
@SuppressWarnings("unchecked")
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 6747004e8186..7cc3dab5866c 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -16,15 +16,13 @@
package android.app;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
+import android.annotation.NonNull;
import android.content.Context;
-import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* Specialization of {@link SecurityException} that contains additional
@@ -35,18 +33,11 @@ import com.android.internal.util.Preconditions;
* authentication credentials, or granting access.
* <p>
* If the receiving app is actively involved with the user, it should present
- * the contained recovery details to help the user make forward progress. The
- * {@link #showAsDialog(Activity)} and
- * {@link #showAsNotification(Context, String)} methods are provided as a
- * convenience, but receiving apps are encouraged to use
- * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more
- * natural way if relevant.
+ * the contained recovery details to help the user make forward progress.
* <p class="note">
* Note: legacy code that receives this exception may treat it as a general
* {@link SecurityException}, and thus there is no guarantee that the messages
* contained will be shown to the end user.
- *
- * @hide
*/
public final class RecoverableSecurityException extends SecurityException implements Parcelable {
private static final String TAG = "RecoverableSecurityException";
@@ -78,53 +69,28 @@ public final class RecoverableSecurityException extends SecurityException implem
* apps that observe {@link Activity#RESULT_OK} may choose to
* immediately retry their operation.
*/
- public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
- RemoteAction userAction) {
+ public RecoverableSecurityException(@NonNull Throwable cause, @NonNull CharSequence userMessage,
+ @NonNull RemoteAction userAction) {
super(cause.getMessage());
- mUserMessage = Preconditions.checkNotNull(userMessage);
- mUserAction = Preconditions.checkNotNull(userAction);
- }
-
- /** {@hide} */
- @Deprecated
- public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
- CharSequence userActionTitle, PendingIntent userAction) {
- this(cause, userMessage,
- new RemoteAction(
- Icon.createWithResource("android",
- com.android.internal.R.drawable.ic_restart),
- userActionTitle, userActionTitle, userAction));
+ mUserMessage = Objects.requireNonNull(userMessage);
+ mUserAction = Objects.requireNonNull(userAction);
}
/**
* Return short message describing the issue for end user audiences, which
* may be shown in a notification or dialog.
*/
- public CharSequence getUserMessage() {
+ public @NonNull CharSequence getUserMessage() {
return mUserMessage;
}
/**
* Return primary action that will initiate the recovery.
*/
- public RemoteAction getUserAction() {
+ public @NonNull RemoteAction getUserAction() {
return mUserAction;
}
- /** @removed */
- @Deprecated
- public void showAsNotification(Context context) {
- final NotificationManager nm = context.getSystemService(NotificationManager.class);
-
- // Create a channel per-sender, since we don't want one poorly behaved
- // remote app to cause all of our notifications to be blocked
- final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
- nm.createNotificationChannel(new NotificationChannel(channelId, TAG,
- NotificationManager.IMPORTANCE_DEFAULT));
-
- showAsNotification(context, channelId);
- }
-
/**
* Convenience method that will show a very simple notification populated
* with the details from this exception.
@@ -142,6 +108,7 @@ public final class RecoverableSecurityException extends SecurityException implem
* @param channelId the {@link NotificationChannel} to use, which must have
* been already created using
* {@link NotificationManager#createNotificationChannel}.
+ * @hide
*/
public void showAsNotification(Context context, String channelId) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -167,6 +134,8 @@ public final class RecoverableSecurityException extends SecurityException implem
* <p>
* This method will only display the most recent exception from any single
* remote UID; dialogs from older exceptions will always be replaced.
+ *
+ * @hide
*/
public void showAsDialog(Activity activity) {
final LocalDialog dialog = new LocalDialog();
diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java
index 85fe99d95969..392921e813f9 100644
--- a/core/java/android/app/RemoteInput.java
+++ b/core/java/android/app/RemoteInput.java
@@ -93,6 +93,22 @@ public final class RemoteInput implements Parcelable {
/** The user selected one of the choices from {@link #getChoices}. */
public static final int SOURCE_CHOICE = 1;
+ /** @hide */
+ @IntDef(prefix = {"EDIT_CHOICES_BEFORE_SENDING_"},
+ value = {EDIT_CHOICES_BEFORE_SENDING_AUTO, EDIT_CHOICES_BEFORE_SENDING_DISABLED,
+ EDIT_CHOICES_BEFORE_SENDING_ENABLED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EditChoicesBeforeSending {}
+
+ /** The platform will determine whether choices will be edited before being sent to the app. */
+ public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0;
+
+ /** Tapping on a choice should send the input immediately, without letting the user edit it. */
+ public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1;
+
+ /** Tapping on a choice should let the user edit the input before it is sent to the app. */
+ public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2;
+
// Flags bitwise-ored to mFlags
private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1;
@@ -103,17 +119,25 @@ public final class RemoteInput implements Parcelable {
private final CharSequence mLabel;
private final CharSequence[] mChoices;
private final int mFlags;
+ @EditChoicesBeforeSending private final int mEditChoicesBeforeSending;
private final Bundle mExtras;
private final ArraySet<String> mAllowedDataTypes;
private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
- int flags, Bundle extras, ArraySet<String> allowedDataTypes) {
+ int flags, int editChoicesBeforeSending, Bundle extras,
+ ArraySet<String> allowedDataTypes) {
this.mResultKey = resultKey;
this.mLabel = label;
this.mChoices = choices;
this.mFlags = flags;
+ this.mEditChoicesBeforeSending = editChoicesBeforeSending;
this.mExtras = extras;
this.mAllowedDataTypes = allowedDataTypes;
+ if (getEditChoicesBeforeSending() == EDIT_CHOICES_BEFORE_SENDING_ENABLED
+ && !getAllowFreeFormInput()) {
+ throw new IllegalArgumentException(
+ "setEditChoicesBeforeSending requires setAllowFreeFormInput");
+ }
}
/**
@@ -149,7 +173,7 @@ public final class RemoteInput implements Parcelable {
/**
* Returns true if the input only accepts data, meaning {@link #getAllowFreeFormInput}
- * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes is
+ * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes} is
* non-null and not empty.
*/
public boolean isDataOnly() {
@@ -169,6 +193,15 @@ public final class RemoteInput implements Parcelable {
}
/**
+ * Gets whether tapping on a choice should let the user edit the input before it is sent to the
+ * app.
+ */
+ @EditChoicesBeforeSending
+ public int getEditChoicesBeforeSending() {
+ return mEditChoicesBeforeSending;
+ }
+
+ /**
* Get additional metadata carried around with this remote input.
*/
public Bundle getExtras() {
@@ -185,6 +218,8 @@ public final class RemoteInput implements Parcelable {
private CharSequence mLabel;
private CharSequence[] mChoices;
private int mFlags = DEFAULT_FLAGS;
+ @EditChoicesBeforeSending
+ private int mEditChoicesBeforeSending = EDIT_CHOICES_BEFORE_SENDING_AUTO;
/**
* Create a builder object for {@link RemoteInput} objects.
@@ -269,7 +304,20 @@ public final class RemoteInput implements Parcelable {
*/
@NonNull
public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) {
- setFlag(mFlags, allowFreeFormTextInput);
+ setFlag(FLAG_ALLOW_FREE_FORM_INPUT, allowFreeFormTextInput);
+ return this;
+ }
+
+ /**
+ * Specifies whether tapping on a choice should let the user edit the input before it is
+ * sent to the app. The default is {@link #EDIT_CHOICES_BEFORE_SENDING_AUTO}.
+ *
+ * It cannot be used if {@link #setAllowFreeFormInput} has been set to false.
+ */
+ @NonNull
+ public Builder setEditChoicesBeforeSending(
+ @EditChoicesBeforeSending int editChoicesBeforeSending) {
+ mEditChoicesBeforeSending = editChoicesBeforeSending;
return this;
}
@@ -312,8 +360,8 @@ public final class RemoteInput implements Parcelable {
*/
@NonNull
public RemoteInput build() {
- return new RemoteInput(
- mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes);
+ return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mEditChoicesBeforeSending,
+ mExtras, mAllowedDataTypes);
}
}
@@ -322,6 +370,7 @@ public final class RemoteInput implements Parcelable {
mLabel = in.readCharSequence();
mChoices = in.readCharSequenceArray();
mFlags = in.readInt();
+ mEditChoicesBeforeSending = in.readInt();
mExtras = in.readBundle();
mAllowedDataTypes = (ArraySet<String>) in.readArraySet(null);
}
@@ -507,6 +556,7 @@ public final class RemoteInput implements Parcelable {
out.writeCharSequence(mLabel);
out.writeCharSequenceArray(mChoices);
out.writeInt(mFlags);
+ out.writeInt(mEditChoicesBeforeSending);
out.writeBundle(mExtras);
out.writeArraySet(mAllowedDataTypes);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 43e183665435..f4fd5d132069 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -154,7 +154,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
-import android.telephony.rcs.RcsManager;
+import android.telephony.ims.RcsManager;
import android.util.ArrayMap;
import android.util.Log;
import android.view.ContextThemeWrapper;
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 257122d3abe0..2990b577e09d 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -673,6 +673,15 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
+ * Returns true if the windowingMode represents a split window.
+ * @hide
+ */
+ public static boolean isSplitScreenWindowingMode(int windowingMode) {
+ return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+
+ /**
* Returns true if the windows associated with this window configuration can receive input keys.
* @hide
*/
diff --git a/core/java/android/app/admin/DelegatedAdminReceiver.java b/core/java/android/app/admin/DelegatedAdminReceiver.java
new file mode 100644
index 000000000000..960538251c5f
--- /dev/null
+++ b/core/java/android/app/admin/DelegatedAdminReceiver.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS;
+import static android.app.admin.DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.security.KeyChain;
+import android.util.Log;
+
+/**
+ * Base class for delegated apps to handle callbacks related to their delegated capabilities.
+ *
+ * <p>Delegated apps are apps that receive additional capabilities from the profile owner or
+ * device owner apps. Some of these capabilities involve the framework calling into the apps.
+ * To receive these callbacks, delegated apps should subclass this class and override the
+ * appropriate methods here. The subclassed receiver needs to be published in the app's
+ * manifest, with appropriate intent filters to mark which callbacks the receiver is interested
+ * in. An app can have multiple receivers as long as they listen for disjoint set of callbacks.
+ * For the manifest definitions, it must be protected by the
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission to ensure only
+ * the system can trigger these callbacks.
+ *
+ * <p>The callback methods happen on the main thread of the process. Thus long running
+ * operations must be done on another thread. Note that because a receiver
+ * is done once returning from its onReceive function, such long-running operations
+ * should probably be done in a {@link Service}.
+ *
+ * @see DevicePolicyManager#setDelegatedScopes
+ * @see DeviceAdminReceiver
+ */
+public class DelegatedAdminReceiver extends BroadcastReceiver {
+ private static final String TAG = "DelegatedAdminReceiver";
+
+ /**
+ * Allows this receiver to select the alias for a private key and certificate pair for
+ * authentication. If this method returns null, the default {@link android.app.Activity} will
+ * be shown that lets the user pick a private key and certificate pair.
+ *
+ * <p> This callback is only applicable if the delegated app has
+ * {@link DevicePolicyManager#DELEGATION_CERT_SELECTION} capability. Additionally, it must
+ * declare an intent fitler for {@link DeviceAdminReceiver#ACTION_CHOOSE_PRIVATE_KEY_ALIAS}
+ * in the receiver's manifest in order to receive this callback.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @param uid The uid asking for the private key and certificate pair.
+ * @param uri The URI to authenticate, may be null.
+ * @param alias The alias preselected by the client, or null.
+ * @return The private key alias to return and grant access to.
+ * @see KeyChain#choosePrivateKeyAlias
+ */
+ public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
+ String alias) {
+ return null;
+ }
+
+ /**
+ * Called each time a new batch of network logs can be retrieved. This callback method will only
+ * ever be called when network logging is enabled. The logs can only be retrieved while network
+ * logging is enabled.
+ *
+ * <p>If a secondary user or profile is created, this callback won't be received until all users
+ * become affiliated again (even if network logging is enabled). It will also no longer be
+ * possible to retrieve the network logs batch with the most recent {@code batchToken} provided
+ * by this callback. See {@link DevicePolicyManager#setAffiliationIds}.
+ *
+ * <p> This callback is only applicable if the delegated app has
+ * {@link DevicePolicyManager#DELEGATION_NETWORK_LOGGING} capability. Additionally, it must
+ * declare an intent fitler for {@link DeviceAdminReceiver#ACTION_NETWORK_LOGS_AVAILABLE} in the
+ * receiver's manifest in order to receive this callback.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @param batchToken The token representing the current batch of network logs.
+ * @param networkLogsCount The total count of events in the current batch of network logs.
+ * @see DevicePolicyManager#retrieveNetworkLogs
+ */
+ public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+ int networkLogsCount) {
+ }
+
+ /**
+ * Intercept delegated device administrator broadcasts. Implementations should not override
+ * this method; implement the convenience callbacks for each action instead.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) {
+ int uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1);
+ Uri uri = intent.getParcelableExtra(EXTRA_CHOOSE_PRIVATE_KEY_URI);
+ String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS);
+ String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, uri, alias);
+ setResultData(chosenAlias);
+ } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) {
+ long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1);
+ int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0);
+ onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
+ } else {
+ Log.w(TAG, "Unhandled broadcast: " + action);
+ }
+ }
+}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 6fb0d7ec33ae..5a7124e1fdc6 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -296,7 +296,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
/**
* Broadcast action: notify that a new batch of network logs is ready to be collected.
* @see DeviceAdminReceiver#onNetworkLogsAvailable
- * @hide
+ * @see DelegatedAdminReceiver#onNetworkLogsAvailable
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@BroadcastBehavior(explicitOnly = true)
@@ -425,7 +425,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
*/
public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1;
- /** @hide */
+ /**
+ * Broadcast action: notify that some app is attempting to choose a KeyChain key.
+ * @see DeviceAdminReceiver#onChoosePrivateKeyAlias
+ * @see DelegatedAdminReceiver#onChoosePrivateKeyAlias
+ */
public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS =
"android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 81eac5a413a6..03e5933d300d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.IntDef;
@@ -66,6 +67,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.UserOperationException;
import android.os.UserManager.UserOperationResult;
+import android.provider.CalendarContract;
import android.provider.ContactsContract.Directory;
import android.provider.Settings;
import android.security.AttestedKeyPair;
@@ -1381,6 +1383,73 @@ public class DevicePolicyManager {
= "android.app.action.SET_NEW_PASSWORD";
/**
+ * Constant for {@link #getPasswordComplexity()}: no password.
+ *
+ * <p>Note that these complexity constants are ordered so that higher values are more complex.
+ */
+ public static final int PASSWORD_COMPLEXITY_NONE = 0;
+
+ /**
+ * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following:
+ * <ul>
+ * <li>pattern
+ * <li>PIN with repeating (4444) or ordered (1234, 4321, 2468) sequences
+ * </ul>
+ *
+ * <p>Note that these complexity constants are ordered so that higher values are more complex.
+ *
+ * @see #PASSWORD_QUALITY_SOMETHING
+ * @see #PASSWORD_QUALITY_NUMERIC
+ */
+ public static final int PASSWORD_COMPLEXITY_LOW = 0x10000;
+
+ /**
+ * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following:
+ * <ul>
+ * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at
+ * least 4
+ * <li>alphabetic, length at least 4
+ * <li>alphanumeric, length at least 4
+ * </ul>
+ *
+ * <p>Note that these complexity constants are ordered so that higher values are more complex.
+ *
+ * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX
+ * @see #PASSWORD_QUALITY_ALPHABETIC
+ * @see #PASSWORD_QUALITY_ALPHANUMERIC
+ */
+ public static final int PASSWORD_COMPLEXITY_MEDIUM = 0x30000;
+
+ /**
+ * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following:
+ * <ul>
+ * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at
+ * least 4
+ * <li>alphabetic, length at least 6
+ * <li>alphanumeric, length at least 6
+ * </ul>
+ *
+ * <p>Note that these complexity constants are ordered so that higher values are more complex.
+ *
+ * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX
+ * @see #PASSWORD_QUALITY_ALPHABETIC
+ * @see #PASSWORD_QUALITY_ALPHANUMERIC
+ */
+ public static final int PASSWORD_COMPLEXITY_HIGH = 0x50000;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"PASSWORD_COMPLEXITY_"}, value = {
+ PASSWORD_COMPLEXITY_NONE,
+ PASSWORD_COMPLEXITY_LOW,
+ PASSWORD_COMPLEXITY_MEDIUM,
+ PASSWORD_COMPLEXITY_HIGH,
+ })
+ public @interface PasswordComplexity {}
+
+ /**
* Activity action: have the user enter a new password for the parent profile.
* If the intent is launched from within a managed profile, this will trigger
* entering a new password for the parent of the profile. In all other cases
@@ -1546,12 +1615,46 @@ public class DevicePolicyManager {
/**
* Delegation of management of uninstalled packages. This scope grants access to the
- * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs.
+ * {@link #setKeepUninstalledPackages} and {@link #getKeepUninstalledPackages} APIs.
*/
public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES =
"delegation-keep-uninstalled-packages";
/**
+ * Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and
+ * {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving
+ * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer
+ * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
+ * There can be at most one app that has this delegation.
+ * If another app already had delegated network logging access,
+ * it will lose the delegation when a new app is delegated.
+ *
+ * <p> Can only be granted by Device Owner.
+ */
+ public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+
+ /**
+ * Grants access to selection of KeyChain certificates on behalf of requesting apps.
+ * Once granted the app will start receiving
+ * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will
+ * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * There can be at most one app that has this delegation.
+ * If another app already had delegated certificate selection access,
+ * it will lose the delegation when a new app is delegated.
+ *
+ * <p> Can be granted by Device Owner or Profile Owner.
+ */
+ public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
+
+
+ /**
+ * Delegation of silent APK installation via {@link android.content.pm.PackageInstaller} APIs.
+ *
+ * <p> Can only be delegated by Device Owner.
+ */
+ public static final String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation";
+
+ /**
* No management for current user in-effect. This is the default.
* @hide
*/
@@ -3071,6 +3174,33 @@ public class DevicePolicyManager {
}
/**
+ * Returns how complex the current user's screen lock is.
+ *
+ * <p>Note that when called from a profile which uses an unified challenge with its parent, the
+ * screen lock complexity of the parent will be returned. However, this API does not support
+ * explicitly querying the parent profile screen lock complexity via {@link
+ * #getParentProfileInstance}.
+ *
+ * @throws IllegalStateException if the user is not unlocked.
+ * @throws SecurityException if the calling application does not have the permission
+ * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}
+ */
+ @PasswordComplexity
+ @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+ public int getPasswordComplexity() {
+ throwIfParentInstance("getPasswordComplexity");
+ if (mService == null) {
+ return PASSWORD_COMPLEXITY_NONE;
+ }
+
+ try {
+ return mService.getPasswordComplexity();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* When called by a profile owner of a managed profile returns true if the profile uses unified
* challenge with its parent user.
*
@@ -9238,7 +9368,8 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner to control the network logging feature.
+ * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to
+ * control the network logging feature.
*
* <p> Network logs contain DNS lookup and connect() library call events. The following library
* functions are recorded while network logging is active:
@@ -9275,16 +9406,17 @@ public class DevicePolicyManager {
* all users to become affiliated. Therefore it's recommended that affiliation ids are set for
* new users as soon as possible after provisioning via {@link #setAffiliationIds}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if called by a delegated app.
* @param enabled whether network logging should be enabled or not.
* @throws SecurityException if {@code admin} is not a device owner.
* @see #setAffiliationIds
* @see #retrieveNetworkLogs
*/
- public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) {
+ public void setNetworkLoggingEnabled(@Nullable ComponentName admin, boolean enabled) {
throwIfParentInstance("setNetworkLoggingEnabled");
try {
- mService.setNetworkLoggingEnabled(admin, enabled);
+ mService.setNetworkLoggingEnabled(admin, mContext.getPackageName(), enabled);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -9294,7 +9426,8 @@ public class DevicePolicyManager {
* Return whether network logging is enabled by a device owner.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
- * be {@code null} if the caller has MANAGE_USERS permission.
+ * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING}
+ * or has MANAGE_USERS permission.
* @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner and caller has
* no MANAGE_USERS permission
@@ -9302,14 +9435,15 @@ public class DevicePolicyManager {
public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isNetworkLoggingEnabled");
try {
- return mService.isNetworkLoggingEnabled(admin);
+ return mService.isNetworkLoggingEnabled(admin, mContext.getPackageName());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
- * Called by device owner to retrieve the most recent batch of network logging events.
+ * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve
+ * the most recent batch of network logging events.
* A device owner has to provide a batchToken provided as part of
* {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
* token of the most recent available batch of logs, {@code null} will be returned.
@@ -9328,7 +9462,8 @@ public class DevicePolicyManager {
* by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
* {@link DevicePolicyManager#setAffiliationIds}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if called by a delegated app.
* @param batchToken A token of the batch to retrieve
* @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
* {@code null} if the batch represented by batchToken is no longer available or if
@@ -9338,11 +9473,11 @@ public class DevicePolicyManager {
* @see #setAffiliationIds
* @see DeviceAdminReceiver#onNetworkLogsAvailable
*/
- public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin,
+ public @Nullable List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin,
long batchToken) {
throwIfParentInstance("retrieveNetworkLogs");
try {
- return mService.retrieveNetworkLogs(admin, batchToken);
+ return mService.retrieveNetworkLogs(admin, mContext.getPackageName(), batchToken);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -10308,4 +10443,33 @@ public class DevicePolicyManager {
}
return false;
}
+
+ /**
+ * Starts an activity to view calendar events in the managed profile.
+ *
+ * @param eventId the id of the event to be viewed.
+ * @param start the start time of the event.
+ * @param end the end time of the event.
+ * @param allDay if the event is an all-day event.
+ * @param flags flags to be set for the intent
+ * @return {@code true} if the activity is started successfully. {@code false} otherwise.
+ *
+ * @see CalendarContract#startViewCalendarEventInManagedProfile(Context, String, long, long,
+ * long, boolean, int)
+ *
+ * @hide
+ */
+ public boolean startViewCalendarEventInManagedProfile(long eventId, long start, long end,
+ boolean allDay, int flags) {
+ throwIfParentInstance("startViewCalendarEventInManagedProfile");
+ if (mService != null) {
+ try {
+ return mService.startViewCalendarEventInManagedProfile(mContext.getPackageName(),
+ eventId, start, end, allDay, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index de9297897158..8765760b216b 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -118,6 +118,12 @@ public abstract class DevicePolicyManagerInternal {
public abstract boolean isUserAffiliatedWithDevice(int userId);
/**
+ * Returns whether the calling package can install or uninstall packages without user
+ * interaction.
+ */
+ public abstract boolean canSilentlyInstallPackage(String callerPackage, int callerUid);
+
+ /**
* Reports that a profile has changed to use a unified or separate credential.
*
* @param userId User ID of the profile.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1ff414619be1..568becfcdd1a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -82,6 +82,7 @@ interface IDevicePolicyManager {
boolean isActivePasswordSufficient(int userHandle, boolean parent);
boolean isProfileActivePasswordSufficientForParent(int userHandle);
+ int getPasswordComplexity();
boolean isUsingUnifiedPassword(in ComponentName admin);
int getCurrentFailedPasswordAttempts(int userHandle, boolean parent);
int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent);
@@ -366,9 +367,9 @@ interface IDevicePolicyManager {
void setBackupServiceEnabled(in ComponentName admin, boolean enabled);
boolean isBackupServiceEnabled(in ComponentName admin);
- void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled);
- boolean isNetworkLoggingEnabled(in ComponentName admin);
- List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken);
+ void setNetworkLoggingEnabled(in ComponentName admin, in String packageName, boolean enabled);
+ boolean isNetworkLoggingEnabled(in ComponentName admin, in String packageName);
+ List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, in String packageName, long batchToken);
boolean bindDeviceAdminServiceAsUser(in ComponentName admin,
IApplicationThread caller, IBinder token, in Intent service,
@@ -431,4 +432,6 @@ interface IDevicePolicyManager {
boolean isManagedKiosk();
boolean isUnattendedManagedKiosk();
+
+ boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags);
}
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 5fee853275fb..8b41755f6dec 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -16,8 +16,14 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +41,8 @@ public class PasswordMetrics implements Parcelable {
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
+ // TODO(b/120536847): refactor isActivePasswordSufficient logic so that the actual password
+ // quality is not overwritten
public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
public int length = 0;
public int letters = 0;
@@ -46,6 +54,10 @@ public class PasswordMetrics implements Parcelable {
public PasswordMetrics() {}
+ public PasswordMetrics(int quality) {
+ this.quality = quality;
+ }
+
public PasswordMetrics(int quality, int length) {
this.quality = quality;
this.length = length;
@@ -173,6 +185,15 @@ public class PasswordMetrics implements Parcelable {
&& this.nonLetter == o.nonLetter;
}
+ private boolean satisfiesBucket(PasswordMetrics... bucket) {
+ for (PasswordMetrics metrics : bucket) {
+ if (this.quality == metrics.quality) {
+ return this.length >= metrics.length;
+ }
+ }
+ return false;
+ }
+
/*
* Returns the maximum length of a sequential characters. A sequence is defined as
* monotonically increasing characters with a constant interval or the same character repeated.
@@ -254,4 +275,99 @@ public class PasswordMetrics implements Parcelable {
return 0;
}
}
+
+ /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */
+ @PasswordComplexity
+ public int determineComplexity() {
+ for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
+ if (satisfiesBucket(bucket.getMetrics())) {
+ return bucket.mComplexityLevel;
+ }
+ }
+ return PASSWORD_COMPLEXITY_NONE;
+ }
+
+ /**
+ * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
+ */
+ public static class PasswordComplexityBucket {
+ /**
+ * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
+ * {@link PasswordMetrics}.
+ */
+ private static final PasswordComplexityBucket HIGH =
+ new PasswordComplexityBucket(
+ PASSWORD_COMPLEXITY_HIGH,
+ new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+ new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
+ new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 8));
+
+ /**
+ * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
+ * {@link PasswordMetrics}.
+ */
+ private static final PasswordComplexityBucket MEDIUM =
+ new PasswordComplexityBucket(
+ PASSWORD_COMPLEXITY_MEDIUM,
+ new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+ new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
+ new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 4));
+
+ /**
+ * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of
+ * {@link PasswordMetrics}.
+ */
+ private static final PasswordComplexityBucket LOW =
+ new PasswordComplexityBucket(
+ PASSWORD_COMPLEXITY_LOW,
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+
+ /**
+ * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
+ */
+ private static final PasswordComplexityBucket NONE =
+ new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics());
+
+ /** Array containing all buckets from high to low. */
+ private static final PasswordComplexityBucket[] BUCKETS =
+ new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW};
+
+ @PasswordComplexity
+ private final int mComplexityLevel;
+ private final PasswordMetrics[] mMetrics;
+
+ private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
+ PasswordMetrics... metrics) {
+ this.mComplexityLevel = complexityLevel;
+ this.mMetrics = metrics;
+ }
+
+ /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
+ public PasswordMetrics[] getMetrics() {
+ return mMetrics;
+ }
+
+ /** Returns the bucket that {@code complexityLevel} represents. */
+ public static PasswordComplexityBucket complexityLevelToBucket(
+ @PasswordComplexity int complexityLevel) {
+ for (PasswordComplexityBucket bucket : BUCKETS) {
+ if (bucket.mComplexityLevel == complexityLevel) {
+ return bucket;
+ }
+ }
+ return NONE;
+ }
+ }
}
diff --git a/core/java/android/app/usage/EventList.java b/core/java/android/app/usage/EventList.java
index aaae57e526a0..a79ad2fc8607 100644
--- a/core/java/android/app/usage/EventList.java
+++ b/core/java/android/app/usage/EventList.java
@@ -103,4 +103,21 @@ public class EventList {
}
return result;
}
+
+ /**
+ * Remove events of certain type on or after a timestamp.
+ * @param type The type of event to remove.
+ * @param timeStamp the timeStamp on or after which to remove the event.
+ */
+ public void removeOnOrAfter(int type, long timeStamp) {
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ UsageEvents.Event event = mEvents.get(i);
+ if (event.mTimeStamp < timeStamp) {
+ break;
+ }
+ if (event.mEventType == type) {
+ mEvents.remove(i);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3a5975aea628..a06213d77a68 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -50,13 +50,27 @@ public final class UsageEvents implements Parcelable {
public static final int NONE = 0;
/**
+ * @deprecated by {@link #ACTIVITY_RESUMED}
+ */
+ @Deprecated
+ public static final int MOVE_TO_FOREGROUND = 1;
+
+ /**
* An event type denoting that an {@link android.app.Activity} moved to the foreground.
* This event has a package name and class name associated with it and can be retrieved
* using {@link #getPackageName()} and {@link #getClassName()}.
* If a package has multiple activities, this event is reported for each activity that moves
* to foreground.
+ * This event is corresponding to {@link android.app.Activity#onResume()} of the
+ * activity's lifecycle.
*/
- public static final int MOVE_TO_FOREGROUND = 1;
+ public static final int ACTIVITY_RESUMED = MOVE_TO_FOREGROUND;
+
+ /**
+ * @deprecated by {@link #ACTIVITY_PAUSED}
+ */
+ @Deprecated
+ public static final int MOVE_TO_BACKGROUND = 2;
/**
* An event type denoting that an {@link android.app.Activity} moved to the background.
@@ -64,19 +78,21 @@ public final class UsageEvents implements Parcelable {
* using {@link #getPackageName()} and {@link #getClassName()}.
* If a package has multiple activities, this event is reported for each activity that moves
* to background.
+ * This event is corresponding to {@link android.app.Activity#onPause()} of the activity's
+ * lifecycle.
*/
- public static final int MOVE_TO_BACKGROUND = 2;
+ public static final int ACTIVITY_PAUSED = MOVE_TO_BACKGROUND;
/**
* An event type denoting that a component was in the foreground when the stats
- * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}.
+ * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}.
* {@hide}
*/
public static final int END_OF_DAY = 3;
/**
* An event type denoting that a component was in the foreground the previous day.
- * This is effectively treated as a {@link #MOVE_TO_FOREGROUND}.
+ * This is effectively treated as a {@link #ACTIVITY_RESUMED}.
* {@hide}
*/
public static final int CONTINUE_PREVIOUS_DAY = 4;
@@ -207,10 +223,31 @@ public final class UsageEvents implements Parcelable {
public static final int ROLLOVER_FOREGROUND_SERVICE = 22;
/**
+ * An activity becomes invisible on the UI, corresponding to
+ * {@link android.app.Activity#onStop()} of the activity's lifecycle.
+ */
+ public static final int ACTIVITY_STOPPED = 23;
+
+ /**
+ * An activity object is destroyed, corresponding to
+ * {@link android.app.Activity#onDestroy()} of the activity's lifecycle.
+ * {@hide}
+ */
+ public static final int ACTIVITY_DESTROYED = 24;
+
+ /**
+ * The event type demoting that a flush of UsageStatsDatabase to file system. Before the
+ * flush all usage stats need to be updated to latest timestamp to make sure the most
+ * up to date stats are persisted.
+ * @hide
+ */
+ public static final int FLUSH_TO_DISK = 25;
+
+ /**
* Keep in sync with the greatest event type value.
* @hide
*/
- public static final int MAX_EVENT_TYPE = 22;
+ public static final int MAX_EVENT_TYPE = 25;
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
@@ -240,6 +277,12 @@ public final class UsageEvents implements Parcelable {
@UnsupportedAppUsage
public String mClass;
+
+ /**
+ * {@hide}
+ */
+ public int mInstanceId;
+
/**
* {@hide}
*/
@@ -311,9 +354,16 @@ public final class UsageEvents implements Parcelable {
}
/** @hide */
+ public Event(int type, long timeStamp) {
+ mEventType = type;
+ mTimeStamp = timeStamp;
+ }
+
+ /** @hide */
public Event(Event orig) {
mPackage = orig.mPackage;
mClass = orig.mClass;
+ mInstanceId = orig.mInstanceId;
mTimeStamp = orig.mTimeStamp;
mEventType = orig.mEventType;
mConfiguration = orig.mConfiguration;
@@ -342,6 +392,16 @@ public final class UsageEvents implements Parcelable {
}
/**
+ * An activity can be instantiated multiple times, this is the unique activity instance ID.
+ * For non-activity class, instance ID is always zero.
+ * @hide
+ */
+ @SystemApi
+ public int getInstanceId() {
+ return mInstanceId;
+ }
+
+ /**
* The time at which this event occurred, measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
@@ -352,12 +412,14 @@ public final class UsageEvents implements Parcelable {
/**
* The event type.
- *
- * @see #MOVE_TO_BACKGROUND
- * @see #MOVE_TO_FOREGROUND
+ * @see #ACTIVITY_PAUSED
+ * @see #ACTIVITY_RESUMED
* @see #CONFIGURATION_CHANGE
* @see #USER_INTERACTION
* @see #STANDBY_BUCKET_CHANGED
+ * @see #FOREGROUND_SERVICE_START
+ * @see #FOREGROUND_SERVICE_STOP
+ * @see #ACTIVITY_STOPPED
*/
public int getEventType() {
return mEventType;
@@ -576,6 +638,7 @@ public final class UsageEvents implements Parcelable {
}
p.writeInt(packageIndex);
p.writeInt(classIndex);
+ p.writeInt(event.mInstanceId);
p.writeInt(event.mEventType);
p.writeLong(event.mTimeStamp);
@@ -618,6 +681,7 @@ public final class UsageEvents implements Parcelable {
} else {
eventOut.mClass = null;
}
+ eventOut.mInstanceId = p.readInt();
eventOut.mEventType = p.readInt();
eventOut.mTimeStamp = p.readLong();
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 73426e495037..8fb7f4cb4d99 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -16,13 +16,15 @@
package android.app.usage;
-import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
import static android.app.usage.UsageEvents.Event.END_OF_DAY;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND;
import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
import android.annotation.SystemApi;
@@ -31,6 +33,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
+import android.util.SparseIntArray;
/**
* Contains usage statistics for an app package for a specific
@@ -57,13 +60,20 @@ public final class UsageStats implements Parcelable {
public long mEndTimeStamp;
/**
- * Last time used by the user with an explicit action (notification, activity launch)
+ * Last time an activity is at foreground (have focus), this is corresponding to
+ * {@link android.app.usage.UsageEvents.Event#ACTIVITY_RESUMED} event.
* {@hide}
*/
@UnsupportedAppUsage
public long mLastTimeUsed;
/**
+ * Last time an activity is visible.
+ * @hide
+ */
+ public long mLastTimeVisible;
+
+ /**
* Total time this package's activity is in foreground.
* {@hide}
*/
@@ -71,6 +81,12 @@ public final class UsageStats implements Parcelable {
public long mTotalTimeInForeground;
/**
+ * Total time this package's activity is visible.
+ * {@hide}
+ */
+ public long mTotalTimeVisible;
+
+ /**
* Last time foreground service is started.
* {@hide}
*/
@@ -93,31 +109,32 @@ public final class UsageStats implements Parcelable {
*/
public int mAppLaunchCount;
- /** Last activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event.
+ /** Last activity ACTIVITY_RESUMED or ACTIVITY_PAUSED event.
* {@hide}
- * @deprecated use {@link #mLastForegroundActivityEventMap} instead.
+ * @deprecated use {@link #mActivities} instead.
*/
@UnsupportedAppUsage
@Deprecated
public int mLastEvent;
/**
- * If an activity is in foreground, it has one entry in this map.
- * When activity moves to background, it is removed from this map.
- * Key is activity class name.
- * Value is last time this activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event.
+ * If an activity is visible(onStart(), onPause() states) or in foreground (onResume() state),
+ * it has one entry in this map. When an activity becomes invisible (onStop() or onDestroy()),
+ * it is removed from this map.
+ * Key is instanceId of the activity (ActivityRecode appToken hashCode)..
+ * Value is this activity's last event, one of ACTIVITY_RESUMED or
+ * ACTIVITY_PAUSED.
* {@hide}
*/
- public ArrayMap<String, Integer> mLastForegroundActivityEventMap = new ArrayMap<>();
-
+ public SparseIntArray mActivities = new SparseIntArray();
/**
* If a foreground service is started, it has one entry in this map.
- * When a foreground service is stopped, it is removed from this map.
+ * When a foreground service is stopped, it is removed from this set.
* Key is foreground service class name.
- * Value is last foreground service FOREGROUND_SERVICE_START ot FOREGROUND_SERVICE_STOP event.
+ * Value is the foreground service's last event, it is FOREGROUND_SERVICE_START.
* {@hide}
*/
- public ArrayMap<String, Integer> mLastForegroundServiceEventMap = new ArrayMap<>();
+ public ArrayMap<String, Integer> mForegroundServices = new ArrayMap<>();
/**
* {@hide}
@@ -135,14 +152,16 @@ public final class UsageStats implements Parcelable {
mBeginTimeStamp = stats.mBeginTimeStamp;
mEndTimeStamp = stats.mEndTimeStamp;
mLastTimeUsed = stats.mLastTimeUsed;
+ mLastTimeVisible = stats.mLastTimeVisible;
mLastTimeForegroundServiceUsed = stats.mLastTimeForegroundServiceUsed;
mTotalTimeInForeground = stats.mTotalTimeInForeground;
+ mTotalTimeVisible = stats.mTotalTimeVisible;
mTotalTimeForegroundServiceUsed = stats.mTotalTimeForegroundServiceUsed;
mLaunchCount = stats.mLaunchCount;
mAppLaunchCount = stats.mAppLaunchCount;
mLastEvent = stats.mLastEvent;
- mLastForegroundActivityEventMap = stats.mLastForegroundActivityEventMap;
- mLastForegroundServiceEventMap = stats.mLastForegroundServiceEventMap;
+ mActivities = stats.mActivities;
+ mForegroundServices = stats.mForegroundServices;
mChooserCounts = stats.mChooserCounts;
}
@@ -191,6 +210,14 @@ public final class UsageStats implements Parcelable {
}
/**
+ * Get the last time this package's activity is visible in the UI, measured in milliseconds
+ * since the epoch.
+ */
+ public long getLastTimeVisible() {
+ return mLastTimeVisible;
+ }
+
+ /**
* Get the total time this package spent in the foreground, measured in milliseconds.
*/
public long getTotalTimeInForeground() {
@@ -198,6 +225,13 @@ public final class UsageStats implements Parcelable {
}
/**
+ * Get the total time this package's activity is visible in the UI, measured in milliseconds.
+ */
+ public long getTotalTimeVisible() {
+ return mTotalTimeVisible;
+ }
+
+ /**
* Get the last time this package's foreground service was used, measured in milliseconds since
* the epoch.
* <p/>
@@ -224,6 +258,20 @@ public final class UsageStats implements Parcelable {
return mAppLaunchCount;
}
+ private void mergeEventMap(SparseIntArray left, SparseIntArray right) {
+ final int size = right.size();
+ for (int i = 0; i < size; i++) {
+ final int instanceId = right.keyAt(i);
+ final int event = right.valueAt(i);
+ final int index = left.indexOfKey(instanceId);
+ if (index >= 0) {
+ left.put(instanceId, Math.max(left.valueAt(index), event));
+ } else {
+ left.put(instanceId, event);
+ }
+ }
+ }
+
private void mergeEventMap(ArrayMap<String, Integer> left, ArrayMap<String, Integer> right) {
final int size = right.size();
for (int i = 0; i < size; i++) {
@@ -255,15 +303,17 @@ public final class UsageStats implements Parcelable {
if (right.mBeginTimeStamp > mBeginTimeStamp) {
// Even though incoming UsageStat begins after this one, its last time used fields
// may somehow be empty or chronologically preceding the older UsageStat.
- mergeEventMap(mLastForegroundActivityEventMap, right.mLastForegroundActivityEventMap);
- mergeEventMap(mLastForegroundServiceEventMap, right.mLastForegroundServiceEventMap);
+ mergeEventMap(mActivities, right.mActivities);
+ mergeEventMap(mForegroundServices, right.mForegroundServices);
mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed);
+ mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible);
mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed,
right.mLastTimeForegroundServiceUsed);
}
mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
mTotalTimeInForeground += right.mTotalTimeInForeground;
+ mTotalTimeVisible += right.mTotalTimeVisible;
mTotalTimeForegroundServiceUsed += right.mTotalTimeForegroundServiceUsed;
mLaunchCount += right.mLaunchCount;
mAppLaunchCount += right.mAppLaunchCount;
@@ -290,36 +340,76 @@ public final class UsageStats implements Parcelable {
}
/**
- * Tell if an event indicate activity is in foreground or not.
- * @param event the activity event.
- * @return true if activity is in foreground, false otherwise.
- * @hide
+ * Tell if any activity is in foreground.
+ * @return
*/
- private boolean isActivityInForeground(int event) {
- return event == MOVE_TO_FOREGROUND
- || event == CONTINUE_PREVIOUS_DAY;
+ private boolean hasForegroundActivity() {
+ final int size = mActivities.size();
+ for (int i = 0; i < size; i++) {
+ if (mActivities.valueAt(i) == ACTIVITY_RESUMED) {
+ return true;
+ }
+ }
+ return false;
}
/**
- * Tell if an event indicate foreground sevice is started or not.
- * @param event the foreground service event.
- * @return true if foreground service is started, false if stopped.
- * @hide
+ * Tell if any activity is visible.
+ * @return
*/
- private boolean isForegroundServiceStarted(int event) {
- return event == FOREGROUND_SERVICE_START
- || event == CONTINUING_FOREGROUND_SERVICE;
+ private boolean hasVisibleActivity() {
+ final int size = mActivities.size();
+ for (int i = 0; i < size; i++) {
+ final int type = mActivities.valueAt(i);
+ if (type == ACTIVITY_RESUMED
+ || type == ACTIVITY_PAUSED) {
+ return true;
+ }
+ }
+ return false;
}
/**
- * If any activity in foreground or any foreground service is started, the app is considered in
- * use.
- * @return true if in use, false otherwise.
- * @hide
+ * Tell if any foreground service is started.
+ * @return
+ */
+ private boolean anyForegroundServiceStarted() {
+ return !mForegroundServices.isEmpty();
+ }
+
+ /**
+ * Increment total time in foreground and update last time in foreground.
+ * @param timeStamp current timestamp.
+ */
+ private void incrementTimeUsed(long timeStamp) {
+ if (timeStamp > mLastTimeUsed) {
+ mTotalTimeInForeground += timeStamp - mLastTimeUsed;
+ mLastTimeUsed = timeStamp;
+ }
+ }
+
+ /**
+ * Increment total time visible and update last time visible.
+ * @param timeStamp current timestmap.
*/
- private boolean isAppInUse() {
- return !mLastForegroundActivityEventMap.isEmpty()
- || !mLastForegroundServiceEventMap.isEmpty();
+ private void incrementTimeVisible(long timeStamp) {
+ if (timeStamp > mLastTimeVisible) {
+ mTotalTimeVisible += timeStamp - mLastTimeVisible;
+ mLastTimeVisible = timeStamp;
+ }
+ }
+
+ /**
+ * Increment total time foreground service is used and update last time foreground service is
+ * used.
+ * @param timeStamp current timestamp.
+ */
+ private void incrementServiceTimeUsed(long timeStamp) {
+ if (timeStamp > mLastTimeForegroundServiceUsed) {
+ mTotalTimeForegroundServiceUsed +=
+ timeStamp - mLastTimeForegroundServiceUsed;
+ mLastTimeForegroundServiceUsed = timeStamp;
+ }
}
/**
@@ -327,33 +417,63 @@ public final class UsageStats implements Parcelable {
* @param className className of the activity.
* @param timeStamp timeStamp of the event.
* @param eventType type of the event.
+ * @param instanceId hashCode of the ActivityRecord's appToken.
* @hide
*/
- private void updateForegroundActivity(String className, long timeStamp, int eventType) {
- if (eventType != MOVE_TO_BACKGROUND
- && eventType != MOVE_TO_FOREGROUND
- && eventType != END_OF_DAY) {
+ private void updateActivity(String className, long timeStamp, int eventType, int instanceId) {
+ if (eventType != ACTIVITY_RESUMED
+ && eventType != ACTIVITY_PAUSED
+ && eventType != ACTIVITY_STOPPED
+ && eventType != ACTIVITY_DESTROYED) {
return;
}
- final Integer lastEvent = mLastForegroundActivityEventMap.get(className);
- if (lastEvent != null) {
- if (isActivityInForeground(lastEvent)) {
- if (timeStamp > mLastTimeUsed) {
- mTotalTimeInForeground += timeStamp - mLastTimeUsed;
+ // update usage.
+ final int index = mActivities.indexOfKey(instanceId);
+ if (index >= 0) {
+ final int lastEvent = mActivities.valueAt(index);
+ switch (lastEvent) {
+ case ACTIVITY_RESUMED:
+ incrementTimeUsed(timeStamp);
+ incrementTimeVisible(timeStamp);
+ break;
+ case ACTIVITY_PAUSED:
+ incrementTimeVisible(timeStamp);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // update current event.
+ switch(eventType) {
+ case ACTIVITY_RESUMED:
+ if (!hasVisibleActivity()) {
+ // this is the first visible activity.
+ mLastTimeUsed = timeStamp;
+ mLastTimeVisible = timeStamp;
+ } else if (!hasForegroundActivity()) {
+ // this is the first foreground activity.
mLastTimeUsed = timeStamp;
}
- }
- if (eventType == MOVE_TO_BACKGROUND) {
- mLastForegroundActivityEventMap.remove(className);
- } else {
- mLastForegroundActivityEventMap.put(className, eventType);
- }
- } else if (eventType == MOVE_TO_FOREGROUND) {
- if (!isAppInUse()) {
- mLastTimeUsed = timeStamp;
- }
- mLastForegroundActivityEventMap.put(className, eventType);
+ mActivities.put(instanceId, eventType);
+ break;
+ case ACTIVITY_PAUSED:
+ if (!hasVisibleActivity()) {
+ // this is the first visible activity.
+ mLastTimeVisible = timeStamp;
+ }
+ mActivities.put(instanceId, eventType);
+ break;
+ case ACTIVITY_STOPPED:
+ mActivities.put(instanceId, eventType);
+ break;
+ case ACTIVITY_DESTROYED:
+ // remove activity from the map.
+ mActivities.delete(instanceId);
+ break;
+ default:
+ break;
}
}
@@ -366,80 +486,97 @@ public final class UsageStats implements Parcelable {
*/
private void updateForegroundService(String className, long timeStamp, int eventType) {
if (eventType != FOREGROUND_SERVICE_STOP
- && eventType != FOREGROUND_SERVICE_START
- && eventType != ROLLOVER_FOREGROUND_SERVICE) {
+ && eventType != FOREGROUND_SERVICE_START) {
return;
}
- final Integer lastEvent = mLastForegroundServiceEventMap.get(className);
+ final Integer lastEvent = mForegroundServices.get(className);
+ // update usage.
if (lastEvent != null) {
- if (isForegroundServiceStarted(lastEvent)) {
- if (timeStamp > mLastTimeForegroundServiceUsed) {
- mTotalTimeForegroundServiceUsed +=
- timeStamp - mLastTimeForegroundServiceUsed;
+ switch (lastEvent) {
+ case FOREGROUND_SERVICE_START:
+ case CONTINUING_FOREGROUND_SERVICE:
+ incrementServiceTimeUsed(timeStamp);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // update current event.
+ switch (eventType) {
+ case FOREGROUND_SERVICE_START:
+ if (!anyForegroundServiceStarted()) {
mLastTimeForegroundServiceUsed = timeStamp;
}
- }
- if (eventType == FOREGROUND_SERVICE_STOP) {
- mLastForegroundServiceEventMap.remove(className);
- } else {
- mLastForegroundServiceEventMap.put(className, eventType);
- }
- } else if (eventType == FOREGROUND_SERVICE_START) {
- if (!isAppInUse()) {
- mLastTimeForegroundServiceUsed = timeStamp;
- }
- mLastForegroundServiceEventMap.put(className, eventType);
+ mForegroundServices.put(className, eventType);
+ break;
+ case FOREGROUND_SERVICE_STOP:
+ mForegroundServices.remove(className);
+ break;
+ default:
+ break;
}
}
/**
* Update the UsageStats by a activity or foreground service event.
- * @param className class name of a activity or foreground service, could be null to mark
- * END_OF_DAY or rollover.
+ * @param className class name of a activity or foreground service, could be null to if this
+ * is sent to all activities/services in this package.
* @param timeStamp Epoch timestamp in milliseconds.
* @param eventType event type as in {@link UsageEvents.Event}
+ * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
+ * if className is not an activity, instanceId is not used.
* @hide
*/
- public void update(String className, long timeStamp, int eventType) {
+ public void update(String className, long timeStamp, int eventType, int instanceId) {
switch(eventType) {
- case MOVE_TO_BACKGROUND:
- case MOVE_TO_FOREGROUND:
- updateForegroundActivity(className, timeStamp, eventType);
+ case ACTIVITY_RESUMED:
+ case ACTIVITY_PAUSED:
+ case ACTIVITY_STOPPED:
+ case ACTIVITY_DESTROYED:
+ updateActivity(className, timeStamp, eventType, instanceId);
break;
case END_OF_DAY:
- // END_OF_DAY means updating all activities.
- final int size = mLastForegroundActivityEventMap.size();
- for (int i = 0; i < size; i++) {
- final String name = mLastForegroundActivityEventMap.keyAt(i);
- updateForegroundActivity(name, timeStamp, eventType);
+ // END_OF_DAY updates all activities.
+ if (hasForegroundActivity()) {
+ incrementTimeUsed(timeStamp);
+ }
+ if (hasVisibleActivity()) {
+ incrementTimeVisible(timeStamp);
}
break;
- case CONTINUE_PREVIOUS_DAY:
- mLastTimeUsed = timeStamp;
- mLastForegroundActivityEventMap.put(className, eventType);
- break;
- case FOREGROUND_SERVICE_STOP:
case FOREGROUND_SERVICE_START:
+ case FOREGROUND_SERVICE_STOP:
updateForegroundService(className, timeStamp, eventType);
break;
case ROLLOVER_FOREGROUND_SERVICE:
- // ROLLOVER_FOREGROUND_SERVICE means updating all foreground services.
- final int size2 = mLastForegroundServiceEventMap.size();
- for (int i = 0; i < size2; i++) {
- final String name = mLastForegroundServiceEventMap.keyAt(i);
- updateForegroundService(name, timeStamp, eventType);
+ // ROLLOVER_FOREGROUND_SERVICE updates all foreground services.
+ if (anyForegroundServiceStarted()) {
+ incrementServiceTimeUsed(timeStamp);
}
break;
case CONTINUING_FOREGROUND_SERVICE:
mLastTimeForegroundServiceUsed = timeStamp;
- mLastForegroundServiceEventMap.put(className, eventType);
+ mForegroundServices.put(className, eventType);
+ break;
+ case FLUSH_TO_DISK:
+ // update usage of all active activities/services.
+ if (hasForegroundActivity()) {
+ incrementTimeUsed(timeStamp);
+ }
+ if (hasVisibleActivity()) {
+ incrementTimeVisible(timeStamp);
+ }
+ if (anyForegroundServiceStarted()) {
+ incrementServiceTimeUsed(timeStamp);
+ }
break;
default:
break;
}
mEndTimeStamp = timeStamp;
- if (eventType == MOVE_TO_FOREGROUND) {
+ if (eventType == ACTIVITY_RESUMED) {
mLaunchCount += 1;
}
}
@@ -455,8 +592,10 @@ public final class UsageStats implements Parcelable {
dest.writeLong(mBeginTimeStamp);
dest.writeLong(mEndTimeStamp);
dest.writeLong(mLastTimeUsed);
+ dest.writeLong(mLastTimeVisible);
dest.writeLong(mLastTimeForegroundServiceUsed);
dest.writeLong(mTotalTimeInForeground);
+ dest.writeLong(mTotalTimeVisible);
dest.writeLong(mTotalTimeForegroundServiceUsed);
dest.writeInt(mLaunchCount);
dest.writeInt(mAppLaunchCount);
@@ -477,21 +616,26 @@ public final class UsageStats implements Parcelable {
}
dest.writeBundle(allCounts);
- final Bundle foregroundActivityEventBundle = new Bundle();
- final int foregroundEventSize = mLastForegroundActivityEventMap.size();
- for (int i = 0; i < foregroundEventSize; i++) {
- foregroundActivityEventBundle.putInt(mLastForegroundActivityEventMap.keyAt(i),
- mLastForegroundActivityEventMap.valueAt(i));
+ writeSparseIntArray(dest, mActivities);
+ dest.writeBundle(eventMapToBundle(mForegroundServices));
+ }
+
+ private void writeSparseIntArray(Parcel dest, SparseIntArray arr) {
+ final int size = arr.size();
+ dest.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ dest.writeInt(arr.keyAt(i));
+ dest.writeInt(arr.valueAt(i));
}
- dest.writeBundle(foregroundActivityEventBundle);
+ }
- final Bundle foregroundServiceEventBundle = new Bundle();
- final int foregroundServiceEventSize = mLastForegroundServiceEventMap.size();
- for (int i = 0; i < foregroundServiceEventSize; i++) {
- foregroundServiceEventBundle.putInt(mLastForegroundServiceEventMap.keyAt(i),
- mLastForegroundServiceEventMap.valueAt(i));
+ private Bundle eventMapToBundle(ArrayMap<String, Integer> eventMap) {
+ final Bundle bundle = new Bundle();
+ final int size = eventMap.size();
+ for (int i = 0; i < size; i++) {
+ bundle.putInt(eventMap.keyAt(i), eventMap.valueAt(i));
}
- dest.writeBundle(foregroundServiceEventBundle);
+ return bundle;
}
public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
@@ -502,8 +646,10 @@ public final class UsageStats implements Parcelable {
stats.mBeginTimeStamp = in.readLong();
stats.mEndTimeStamp = in.readLong();
stats.mLastTimeUsed = in.readLong();
+ stats.mLastTimeVisible = in.readLong();
stats.mLastTimeForegroundServiceUsed = in.readLong();
stats.mTotalTimeInForeground = in.readLong();
+ stats.mTotalTimeVisible = in.readLong();
stats.mTotalTimeForegroundServiceUsed = in.readLong();
stats.mLaunchCount = in.readInt();
stats.mAppLaunchCount = in.readInt();
@@ -527,12 +673,21 @@ public final class UsageStats implements Parcelable {
}
}
}
- readBundleToEventMap(stats.mLastForegroundActivityEventMap, in.readBundle());
- readBundleToEventMap(stats.mLastForegroundServiceEventMap, in.readBundle());
+ readSparseIntArray(in, stats.mActivities);
+ readBundleToEventMap(in.readBundle(), stats.mForegroundServices);
return stats;
}
- private void readBundleToEventMap(ArrayMap<String, Integer> eventMap, Bundle bundle) {
+ private void readSparseIntArray(Parcel in, SparseIntArray arr) {
+ final int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ final int key = in.readInt();
+ final int value = in.readInt();
+ arr.put(key, value);
+ }
+ }
+
+ private void readBundleToEventMap(Bundle bundle, ArrayMap<String, Integer> eventMap) {
if (bundle != null) {
for (String className : bundle.keySet()) {
final int event = bundle.getInt(className);
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 1a656ab39373..2edad350e18e 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -37,9 +37,12 @@ public abstract class UsageStatsManagerInternal {
* @param component The component for which this event occurred.
* @param userId The user id to which the component belongs to.
* @param eventType The event that occurred. Valid values can be found at
- * {@link UsageEvents}
+ * {@link UsageEvents}
+ * @param instanceId For activity, hashCode of ActivityRecord's appToken.
+ * For non-activity, it is not used.
*/
- public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType);
+ public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
+ int instanceId);
/**
* Reports an event to the UsageStatsManager.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d7d3cb543af9..b39010d9d14e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4462,7 +4462,7 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve an
- * {@link android.telephony.rcs.RcsManager}.
+ * {@link android.telephony.ims.RcsManager}.
* @hide
*/
public static final String TELEPHONY_RCS_SERVICE = "ircs";
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 07d6e4785759..c361ac12667e 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -631,6 +631,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_PROFILEABLE_BY_SHELL = 1 << 23;
+ /**
+ * Indicates whether this package requires access to non-SDK APIs.
+ * Only system apps and tests are allowed to use this property.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_HAS_FRAGILE_USER_DATA = 1 << 24;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
@@ -655,6 +662,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
PRIVATE_FLAG_VENDOR,
PRIVATE_FLAG_VIRTUAL_PRELOAD,
+ PRIVATE_FLAG_HAS_FRAGILE_USER_DATA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlags {}
@@ -1730,6 +1738,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
return (privateFlags & PRIVATE_FLAG_USES_NON_SDK_API) != 0;
}
+ /**
+ * Whether an app needs to keep the app data on uninstall.
+ *
+ * @return {@code true} if the app indicates that it needs to keep the app data
+ *
+ * @hide
+ */
+ public boolean hasFragileUserData() {
+ return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0;
+ }
+
private boolean isAllowedToUseHiddenApis() {
if (isSignedWithPlatformKey()) {
return true;
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
new file mode 100644
index 000000000000..07e640b1ba61
--- /dev/null
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information you can retrieve about a particular system
+ * module.
+ */
+public final class ModuleInfo implements Parcelable {
+
+ // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
+ // constructor, and writeToParcel.
+
+ /** Public name of this module. */
+ private String mName;
+
+ /** The package name of this module. */
+ private String mPackageName;
+
+ /** Whether or not this module is hidden from the user. */
+ private boolean mHidden;
+
+ // TODO: Decide whether we need an additional metadata bundle to support out of band
+ // updates to ModuleInfo.
+ //
+ // private Bundle mMetadata;
+
+ /** @hide */
+ public ModuleInfo() {
+ }
+
+ /** @hide */
+ public ModuleInfo(ModuleInfo orig) {
+ mName = orig.mName;
+ mPackageName = orig.mPackageName;
+ mHidden = orig.mHidden;
+ }
+
+ /** @hide Sets the public name of this module. */
+ public ModuleInfo setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /** Gets the public name of this module. */
+ public @Nullable String getName() {
+ return mName;
+ }
+
+ /** @hide Sets the package name of this module. */
+ public ModuleInfo setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /** Gets the package name of this module. */
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /** @hide Sets whether or not this package is hidden. */
+ public ModuleInfo setHidden(boolean hidden) {
+ mHidden = hidden;
+ return this;
+ }
+
+ /** Gets whether or not this package is hidden. */
+ public boolean isHidden() {
+ return mHidden;
+ }
+
+ /** Returns a string representation of this object. */
+ public String toString() {
+ return "ModuleInfo{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mName + "}";
+ }
+
+ /** Describes the kinds of special objects contained in this object. */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 0;
+ hashCode = 31 * hashCode + Objects.hashCode(mName);
+ hashCode = 31 * hashCode + Objects.hashCode(mPackageName);
+ hashCode = 31 * hashCode + Boolean.hashCode(mHidden);
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ModuleInfo)) {
+ return false;
+ }
+ final ModuleInfo other = (ModuleInfo) obj;
+ return Objects.equals(mName, other.mName)
+ && Objects.equals(mPackageName, other.mPackageName)
+ && mHidden == other.mHidden;
+ }
+
+ /** Flattens this object into the given {@link Parcel}. */
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString(mName);
+ dest.writeString(mPackageName);
+ dest.writeBoolean(mHidden);
+ }
+
+ private ModuleInfo(Parcel source) {
+ mName = source.readString();
+ mPackageName = source.readString();
+ mHidden = source.readBoolean();
+ }
+
+ public static final Parcelable.Creator<ModuleInfo> CREATOR =
+ new Parcelable.Creator<ModuleInfo>() {
+ public ModuleInfo createFromParcel(Parcel source) {
+ return new ModuleInfo(source);
+ }
+ public ModuleInfo[] newArray(int size) {
+ return new ModuleInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 96c30f156105..f81eb7642443 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -68,7 +68,14 @@ import java.util.List;
* {@link PackageInstaller.Session}, which any app can create. Once the session
* is created, the installer can stream one or more APKs into place until it
* decides to either commit or destroy the session. Committing may require user
- * intervention to complete the installation.
+ * intervention to complete the installation, unless the caller falls into one of the
+ * following categories, in which case the installation will complete automatically.
+ * <ul>
+ * <li>the device owner
+ * <li>the affiliated profile owner
+ * <li>the device owner delegated app with
+ * {@link android.app.admin.DevicePolicyManager#DELEGATION_PACKAGE_INSTALLATION}
+ * </ul>
* <p>
* Sessions can install brand new apps, upgrade existing apps, or add new splits
* into an existing app.
@@ -481,6 +488,8 @@ public class PackageInstaller {
* <li>the current "installer of record" for the package
* <li>the device owner
* <li>the affiliated profile owner
+ * <li>the device owner delegated app with
+ * {@link android.app.admin.DevicePolicyManager#DELEGATION_PACKAGE_INSTALLATION}
* </ul>
*
* @param packageName The package to uninstall.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index da39b6394f8f..566017b7372e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -220,6 +220,12 @@ public abstract class PackageManager {
/** @hide */
@IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ModuleInfoFlags {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
GET_META_DATA,
})
@Retention(RetentionPolicy.SOURCE)
@@ -3377,6 +3383,33 @@ public abstract class PackageManager {
@ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
/**
+ * Retrieve all of the information we know about a particular
+ * package/application, for a specific user.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of an
+ * application.
+ * @param flags Additional option flags to modify the data returned.
+ * @return An {@link ApplicationInfo} containing information about the
+ * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if
+ * the package is not found in the list of installed applications,
+ * the application information is retrieved from the list of
+ * uninstalled applications (which includes installed applications
+ * as well as applications with data directory i.e. applications
+ * which had been deleted with {@code DONT_DELETE_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+ @ApplicationInfoFlags int flags, @NonNull UserHandle user)
+ throws NameNotFoundException {
+ return getApplicationInfoAsUser(packageName, flags, user.getIdentifier());
+ }
+
+ /**
* Retrieve all of the information we know about a particular activity
* class.
*
@@ -3440,6 +3473,35 @@ public abstract class PackageManager {
@ComponentInfoFlags int flags) throws NameNotFoundException;
/**
+ * Retrieve information for a particular module.
+ *
+ * @param packageName The name of the module.
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link ModuleInfo} object containing information about the
+ * module.
+ * @throws NameNotFoundException if a module with the given name cannot be
+ * found on the system.
+ */
+ public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException(
+ "getModuleInfo not implemented in subclass");
+ }
+
+ /**
+ * Return a List of all modules that are installed.
+ *
+ * @param flags Additional option flags to modify the data returned.
+ * @return A {@link List} of {@link ModuleInfo} objects, one for each installed
+ * module, containing information about the module. In the unlikely case
+ * there are no installed modules, an empty list is returned.
+ */
+ public @NonNull List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) {
+ throw new UnsupportedOperationException(
+ "getInstalledModules not implemented in subclass");
+ }
+
+ /**
* Return a List of all packages that are installed for the current user.
*
* @param flags Additional option flags to modify the data returned.
@@ -4203,6 +4265,32 @@ public abstract class PackageManager {
@ResolveInfoFlags int flags, @UserIdInt int userId);
/**
+ * Retrieve all activities that can be performed for the given intent, for a
+ * specific user.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags to modify the data returned. The
+ * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+ * resolution to only those activities that support the
+ * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set
+ * {@link #MATCH_ALL} to prevent any filtering of the results.
+ * @param user The user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching activity, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveActivity}. If there are no matching activities, an
+ * empty list is returned.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+ return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier());
+ }
+
+ /**
* Retrieve a set of activities that should be presented to the user as
* similar options. This is like {@link #queryIntentActivities}, except it
* also allows you to supply a list of more explicit Intents that you would
@@ -4334,6 +4422,27 @@ public abstract class PackageManager {
@ResolveInfoFlags int flags, @UserIdInt int userId);
/**
+ * Retrieve all services that can match the given intent for a given user.
+ *
+ * @param intent The desired intent as per resolveService().
+ * @param flags Additional option flags to modify the data returned.
+ * @param user The user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching service, ordered from best to worst. In other
+ * words, the first item is what would be returned by
+ * {@link #resolveService}. If there are no matching services, an
+ * empty list or null is returned.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+ return queryIntentServicesAsUser(intent, flags, user.getIdentifier());
+ }
+
+ /**
* Retrieve all providers that can match the given intent.
*
* @param intent An intent containing all of the desired specification
@@ -4355,6 +4464,26 @@ public abstract class PackageManager {
* @param intent An intent containing all of the desired specification
* (action, data, type, category, and/or component).
* @param flags Additional option flags to modify the data returned.
+ * @param user The user being queried.
+ * @return Returns a List of ResolveInfo objects containing one entry for
+ * each matching provider, ordered from best to worst. If there are
+ * no matching services, an empty list or null is returned.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ @SystemApi
+ public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
+ @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+ return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier());
+ }
+
+ /**
+ * Retrieve all providers that can match the given intent.
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags to modify the data returned.
* @return Returns a List of ResolveInfo objects containing one entry for
* each matching provider, ordered from best to worst. If there are
* no matching services, an empty list or null is returned.
@@ -6448,7 +6577,7 @@ public abstract class PackageManager {
*
* @hide
*/
- @SystemApi
+ @TestApi
public String getWellbeingPackageName() {
throw new UnsupportedOperationException(
"getWellbeingPackageName not implemented in subclass");
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d0de9a1d2a76..61a74ded02d0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3710,6 +3710,12 @@ public class PackageParser {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_hasFragileUserData,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA;
+ }
+
if (outError[0] == null) {
CharSequence pname;
if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 1d9330d4dfed..9d37d9939127 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -28,6 +28,21 @@ import android.hardware.face.FaceManager;
*/
public interface BiometricFaceConstants {
//
+ // Accessibility constants
+ //
+ /**
+ * Require the user to look at the device during enrollment and
+ * authentication. Note this is to accommodate people who have limited
+ * vision.
+ */
+ public static final int FEATURE_REQUIRE_ATTENTION = 1;
+ /**
+ * Require a diverse set of poses during enrollment. Note this is to
+ * accommodate people with limited mobility.
+ */
+ public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2;
+
+ //
// Error messages from face authentication hardware during initialization, enrollment,
// authentication or removal. Must agree with the list in HAL h file
//
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 9d61f028bc91..1af9cdebf6bc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -16,6 +16,7 @@
package android.hardware.display;
+import android.annotation.Nullable;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.PowerManager;
@@ -195,6 +196,44 @@ public abstract class DisplayManagerInternal {
public abstract void onOverlayChanged();
/**
+ * Get the attributes available for display color sampling.
+ * @param displayId id of the display to collect the sample from.
+ *
+ * @return The attributes the display supports, or null if sampling is not supported.
+ */
+ @Nullable
+ public abstract DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+ int displayId);
+
+ /**
+ * Enable or disable the collection of color samples.
+ *
+ * @param displayId id of the display to collect the sample from.
+ * @param componentMask a bitmask of the color channels to collect samples for, or zero for all
+ * available.
+ * @param maxFrames maintain a ringbuffer of the last maxFrames.
+ * @param enable True to enable, False to disable.
+ *
+ * @return True if sampling was enabled, false if failure.
+ */
+ public abstract boolean setDisplayedContentSamplingEnabled(
+ int displayId, boolean enable, int componentMask, int maxFrames);
+
+ /**
+ * Accesses the color histogram statistics of displayed frames on devices that support sampling.
+ *
+ * @param displayId id of the display to collect the sample from
+ * @param maxFrames limit the statistics to the last maxFrames number of frames.
+ * @param timestamp discard statistics that were collected prior to timestamp, where timestamp
+ * is given as CLOCK_MONOTONIC.
+ * @return The statistics representing a histogram of the color distribution of the frames
+ * displayed on-screen, or null if sampling is not supported.
+ */
+ @Nullable
+ public abstract DisplayedContentSample getDisplayedContentSample(
+ int displayId, long maxFrames, long timestamp);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/DisplayedContentSample.java b/core/java/android/hardware/display/DisplayedContentSample.java
new file mode 100644
index 000000000000..0610377c648a
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayedContentSample.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+/**
+ * @hide
+ */
+public final class DisplayedContentSample {
+ private long mNumFrames;
+ private long[] mSamplesComponent0;
+ private long[] mSamplesComponent1;
+ private long[] mSamplesComponent2;
+ private long[] mSamplesComponent3;
+
+ /**
+ * Construct an object representing a color histogram of pixels that were displayed on screen.
+ *
+ * @param numFrames The number of frames represented by this sample.
+ * @param mSamplesComponent0 is a histogram counting how many times a pixel of a given value
+ * was displayed onscreen for FORMAT_COMPONENT_0. The buckets of the histogram are evenly
+ * weighted, the number of buckets is device specific.
+ * eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that 10 red pixels were
+ * displayed onscreen in range 0x00->0x3F, 6 red pixels were displayed onscreen in range
+ * 0x40->0x7F, etc.
+ * @param mSamplesComponent1 is the same sample definition as sampleComponent0, but for the
+ * second component of format.
+ * @param mSamplesComponent2 is the same sample definition as sampleComponent0, but for the
+ * third component of format.
+ * @param mSamplesComponent3 is the same sample definition as sampleComponent0, but for the
+ * fourth component of format.
+ */
+ public DisplayedContentSample(long numFrames,
+ long[] sampleComponent0,
+ long[] sampleComponent1,
+ long[] sampleComponent2,
+ long[] sampleComponent3) {
+ mNumFrames = numFrames;
+ mSamplesComponent0 = sampleComponent0;
+ mSamplesComponent1 = sampleComponent1;
+ mSamplesComponent2 = sampleComponent2;
+ mSamplesComponent3 = sampleComponent3;
+ }
+
+ public enum ColorComponent {
+ CHANNEL0,
+ CHANNEL1,
+ CHANNEL2,
+ CHANNEL3,
+ }
+
+ /**
+ * Returns a color histogram according to component channel.
+ *
+ * @param component the component to return, according to the PixelFormat ordering
+ * (eg, for RGBA, CHANNEL0 is R, CHANNEL1 is G, etc).
+ *
+ * @return an evenly weighted histogram counting how many times a pixel was
+ * displayed onscreen that fell into the corresponding bucket, with the first entry
+ * corresponding to the normalized 0.0 value, and the last corresponding to the 1.0
+ * value for that PixelFormat component.
+ */
+ public long[] getSampleComponent(ColorComponent component) {
+ switch (component) {
+ case CHANNEL0: return mSamplesComponent0;
+ case CHANNEL1: return mSamplesComponent1;
+ case CHANNEL2: return mSamplesComponent2;
+ case CHANNEL3: return mSamplesComponent3;
+ default: throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Return the number of frames this sample was collected over.
+ *
+ * @return the number of frames that this sample was collected over.
+ */
+ public long getNumFrames() {
+ return mNumFrames;
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java
new file mode 100644
index 000000000000..aad68d9bcf09
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+/**
+ * @hide
+ */
+public final class DisplayedContentSamplingAttributes {
+ private int mPixelFormat;
+ private int mDataspace;
+ private int mComponentMask;
+
+ /* Creates the attributes reported by the display hardware about what capabilities
+ * are present.
+ *
+ * NOTE: the format and ds constants must match the values from graphics/common/x.x/types.hal
+ * @param format the format that the display hardware samples in.
+ * @param ds the dataspace in use when sampling.
+ * @param componentMask a mask of which of the format components are supported.
+ */
+ public DisplayedContentSamplingAttributes(int format, int ds, int componentMask) {
+ mPixelFormat = format;
+ mDataspace = ds;
+ mComponentMask = componentMask;
+ }
+
+ /* Returns the pixel format that the display hardware uses when sampling.
+ *
+ * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal
+ * @return the format that the samples were collected in.
+ */
+ public int getPixelFormat() {
+ return mPixelFormat;
+ }
+
+ /* Returns the dataspace that the display hardware uses when sampling.
+ *
+ * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal
+ * @return the dataspace that the samples were collected in.
+ */
+ public int getDataspace() {
+ return mDataspace;
+ }
+
+ /* Returns a mask of which components can be collected by the sampling engine.
+ *
+ * @return a mask of the components which are supported by the engine. The lowest
+ * bit corresponds to the lowest component (ie, 0x1 corresponds to A for RGBA).
+ */
+ public int getComponentMask() {
+ return mComponentMask;
+ }
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 322863a6577d..bac23b3c00f9 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -207,11 +207,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
- public void enroll(byte[] token, CancellationSignal cancel, int flags,
- int userId, EnrollmentCallback callback) {
- if (userId == UserHandle.USER_CURRENT) {
- userId = getCurrentUserId();
- }
+ public void enroll(byte[] token, CancellationSignal cancel,
+ EnrollmentCallback callback, int[] disabledFeatures) {
if (callback == null) {
throw new IllegalArgumentException("Must supply an enrollment callback");
}
@@ -228,8 +225,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService != null) {
try {
mEnrollmentCallback = callback;
- mService.enroll(mToken, token, userId, mServiceReceiver, flags,
- mContext.getOpPackageName());
+ mService.enroll(mToken, token, mServiceReceiver,
+ mContext.getOpPackageName(), disabledFeatures);
} catch (RemoteException e) {
Log.w(TAG, "Remote exception in enroll: ", e);
if (callback != null) {
@@ -284,10 +281,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
- public void setRequireAttention(boolean requireAttention, byte[] token) {
+ public void setFeature(int feature, boolean enabled, byte[] token) {
if (mService != null) {
try {
- mService.setRequireAttention(requireAttention, token);
+ mService.setFeature(feature, enabled, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -298,11 +295,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
- public boolean getRequireAttention(byte[] token) {
+ public boolean getFeature(int feature) {
boolean result = true;
if (mService != null) {
try {
- mService.getRequireAttention(token);
+ result = mService.getFeature(feature);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index a15dcec3b276..a1c88f81e3e7 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -50,8 +50,8 @@ interface IFaceService {
int callingUid, int callingPid, int callingUserId, boolean fromClient);
// Start face enrollment
- void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,
- int flags, String opPackageName);
+ void enroll(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
+ String opPackageName, in int [] disabledFeatures);
// Cancel enrollment in progress
void cancelEnrollment(IBinder token);
@@ -98,9 +98,9 @@ interface IFaceService {
// Enumerate all faces
void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver);
- int setRequireAttention(boolean requireAttention, in byte [] token);
+ int setFeature(int feature, boolean enabled, in byte [] token);
- boolean getRequireAttention(in byte [] token);
+ boolean getFeature(int feature);
void userActivity();
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d45fa11de639..9939a3c8f36d 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
@@ -399,6 +400,9 @@ public class Binder implements IBinder {
* reasons, we only support one UID. This UID represents the original user responsible for the
* binder calls.
*
+ * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after setting the
+ * worksource.
+ *
* <p>A typical use case would be
* <pre>
* long token = Binder.setCallingWorkSourceUid(uid);
@@ -417,6 +421,7 @@ public class Binder implements IBinder {
* @hide
**/
@CriticalNative
+ @SystemApi
public static final native long setCallingWorkSourceUid(int workSource);
/**
@@ -430,6 +435,7 @@ public class Binder implements IBinder {
* @hide
*/
@CriticalNative
+ @SystemApi
public static final native int getCallingWorkSourceUid();
/**
@@ -438,10 +444,24 @@ public class Binder implements IBinder {
* <p>The work source will be propagated for future outgoing binder transactions
* executed on this thread.
*
+ * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after clearing the
+ * worksource.
+ *
+ * <p>A typical use case would be
+ * <pre>
+ * long token = Binder.clearCallingWorkSource();
+ * try {
+ * // Call an API.
+ * } finally {
+ * Binder.restoreCallingWorkSource(token);
+ * }
+ * </pre>
+ *
* @return token to restore original work source.
* @hide
**/
@CriticalNative
+ @SystemApi
public static final native long clearCallingWorkSource();
/**
@@ -461,6 +481,7 @@ public class Binder implements IBinder {
* @hide
**/
@CriticalNative
+ @SystemApi
public static final native void restoreCallingWorkSource(long token);
/**
@@ -601,6 +622,7 @@ public class Binder implements IBinder {
* See {@link setProxyTransactListener}.
* @hide
*/
+ @SystemApi
public interface ProxyTransactListener {
/**
* Called before onTransact.
@@ -663,6 +685,7 @@ public class Binder implements IBinder {
* <li>Never execute another binder transaction inside the listener.
* @hide
*/
+ @SystemApi
public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) {
BinderProxy.setTransactListener(listener);
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 7abe913312a1..124d7b174739 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -16,7 +16,6 @@
package android.os;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -71,7 +70,7 @@ public class GraphicsEnvironment {
*/
public void setup(Context context, Bundle coreSettings) {
setupGpuLayers(context, coreSettings);
- setupAngle(context, context.getPackageName());
+ setupAngle(context, coreSettings, context.getPackageName());
chooseDriver(context, coreSettings);
}
@@ -213,10 +212,9 @@ public class GraphicsEnvironment {
}
- private static List<String> getGlobalSettingsString(Context context, String globalSetting) {
+ private static List<String> getGlobalSettingsString(Bundle bundle, String globalSetting) {
List<String> valueList = null;
- ContentResolver contentResolver = context.getContentResolver();
- String settingsValue = Settings.Global.getString(contentResolver, globalSetting);
+ String settingsValue = bundle.getString(globalSetting);
if (settingsValue != null) {
valueList = new ArrayList<>(Arrays.asList(settingsValue.split(",")));
@@ -238,23 +236,18 @@ public class GraphicsEnvironment {
return -1;
}
- private static String getDriverForPkg(Context context, String packageName) {
- try {
- ContentResolver contentResolver = context.getContentResolver();
- int allUseAngle = Settings.Global.getInt(contentResolver,
- Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
- if (allUseAngle == 1) {
- return sDriverMap.get(OpenGlDriverChoice.ANGLE);
- }
- } catch (Settings.SettingNotFoundException e) {
- // Do nothing and move on
+ private static String getDriverForPkg(Bundle bundle, String packageName) {
+ String allUseAngle =
+ bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+ if ((allUseAngle != null) && allUseAngle.equals("1")) {
+ return sDriverMap.get(OpenGlDriverChoice.ANGLE);
}
List<String> globalSettingsDriverPkgs =
- getGlobalSettingsString(context,
+ getGlobalSettingsString(bundle,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS);
List<String> globalSettingsDriverValues =
- getGlobalSettingsString(context,
+ getGlobalSettingsString(bundle,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
// Make sure we have a good package name
@@ -285,8 +278,8 @@ public class GraphicsEnvironment {
/**
* Pass ANGLE details down to trigger enable logic
*/
- private void setupAngle(Context context, String packageName) {
- String devOptIn = getDriverForPkg(context, packageName);
+ private void setupAngle(Context context, Bundle bundle, String packageName) {
+ String devOptIn = getDriverForPkg(bundle, packageName);
if (DEBUG) {
Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 17ce79b83aa8..0a60764428dc 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1515,11 +1515,11 @@ public class UserManager {
* background user; the result here does not distinguish between the two.
*
* <p>Note prior to Android Nougat MR1 (SDK version <= 24;
- * {@link android.os.Build.VERSION_CODES#N), this API required a system permission
+ * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission
* in order to check other profile's status.
* Since Android Nougat MR1 (SDK version >= 25;
- * {@link android.os.Build.VERSION_CODES#N_MR1)), the restriction has been relaxed, and now
- * it'll accept any {@link UserHandle} within the same profile group as the caller.
+ * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now
+ * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller.
*
* @param user The user to retrieve the running state for.
*/
@@ -1544,11 +1544,11 @@ public class UserManager {
* (but is not yet fully stopped, and still running some code).
*
* <p>Note prior to Android Nougat MR1 (SDK version <= 24;
- * {@link android.os.Build.VERSION_CODES#N), this API required a system permission
+ * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission
* in order to check other profile's status.
* Since Android Nougat MR1 (SDK version >= 25;
- * {@link android.os.Build.VERSION_CODES#N_MR1)), the restriction has been relaxed, and now
- * it'll accept any {@link UserHandle} within the same profile group as the caller.
+ * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now
+ * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller.
*
* @param user The user to retrieve the running state for.
*/
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 7fd0a4b66d66..f136cd6699a7 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -398,6 +398,8 @@ public class ZygoteProcess {
argsForZygote.add("--mount-external-write");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) {
argsForZygote.add("--mount-external-full");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
+ argsForZygote.add("--mount-external-installer");
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 865b8f8482bd..c167ea18f0c5 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.UnsupportedAppUsage;
@@ -41,6 +42,8 @@ import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
/**
* <p>
* The contract between the calendar provider and applications. Contains
@@ -129,6 +132,13 @@ public final class CalendarContract {
"android.provider.calendar.action.HANDLE_CUSTOM_EVENT";
/**
+ * Action used to help apps show calendar events in the managed profile.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VIEW_WORK_CALENDAR_EVENT =
+ "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT";
+
+ /**
* Intent Extras key: {@link EventsColumns#CUSTOM_APP_URI} for the event in
* the {@link #ACTION_HANDLE_CUSTOM_EVENT} intent
*/
@@ -153,6 +163,11 @@ public final class CalendarContract {
public static final String EXTRA_EVENT_ALL_DAY = "allDay";
/**
+ * Intent Extras key: The id of an event.
+ */
+ public static final String EXTRA_EVENT_ID = "id";
+
+ /**
* This authority is used for writing to or querying from the calendar
* provider. Note: This is set at first run and cannot be changed without
* breaking apps that access the provider.
@@ -195,6 +210,43 @@ public final class CalendarContract {
private CalendarContract() {}
/**
+ * Starts an activity to view calendar events in the managed profile.
+ *
+ * When this API is called, the system will attempt to start an activity
+ * in the managed profile with an intent targeting the same caller package.
+ * The intent will have its action set to
+ * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras
+ * corresponding to the API's arguments. A calendar app intending to support
+ * cross profile events viewing should handle this intent, parse the arguments
+ * and show the appropriate UI.
+ *
+ * @param context the context.
+ * @param eventId the id of the event to be viewed. Will be put into {@link #EXTRA_EVENT_ID}
+ * field of the intent.
+ * @param start the start time of the event. Will be put into {@link #EXTRA_EVENT_BEGIN_TIME}
+ * field of the intent.
+ * @param end the end time of the event. Will be put into {@link #EXTRA_EVENT_END_TIME} field
+ * of the intent.
+ * @param allDay if the event is an all-day event. Will be put into
+ * {@link #EXTRA_EVENT_ALL_DAY} field of the intent.
+ * @param flags flags to be set on the intent via {@link Intent#setFlags}
+ * @return {@code true} if the activity is started successfully. {@code false} otherwise.
+ *
+ * @see #EXTRA_EVENT_ID
+ * @see #EXTRA_EVENT_BEGIN_TIME
+ * @see #EXTRA_EVENT_END_TIME
+ * @see #EXTRA_EVENT_ALL_DAY
+ */
+ public static boolean startViewCalendarEventInManagedProfile(@NonNull Context context,
+ long eventId, long start, long end, boolean allDay, int flags) {
+ Preconditions.checkNotNull(context, "Context is null");
+ final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ return dpm.startViewCalendarEventInManagedProfile(eventId, start,
+ end, allDay, flags);
+ }
+
+ /**
* Generic columns for use by sync adapters. The specific functions of these
* columns are private to the sync adapter. Other clients of the API should
* not attempt to either read or write this column. These columns are
@@ -695,7 +747,7 @@ public final class CalendarContract {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
/**
- * The content:// style URL for querying Calendars table in the work profile. Appending a
+ * The content:// style URL for querying Calendars table in the managed profile. Appending a
* calendar id using {@link ContentUris#withAppendedId(Uri, long)} will
* specify a single calendar.
*
@@ -715,9 +767,9 @@ public final class CalendarContract {
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a work profile, or cross profile calendar is disabled in Settings, or this uri is
- * queried from a package that is not whitelisted by profile owner of the work profile via
- * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * queried from a package that is not whitelisted by profile owner of the managed profile
+ * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1673,7 +1725,7 @@ public final class CalendarContract {
Uri.parse("content://" + AUTHORITY + "/events");
/**
- * The content:// style URL for querying Events table in the work profile. Appending an
+ * The content:// style URL for querying Events table in the managed profile. Appending an
* event id using {@link ContentUris#withAppendedId(Uri, long)} will
* specify a single event.
*
@@ -1706,9 +1758,9 @@ public final class CalendarContract {
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a work profile, or cross profile calendar is disabled in Settings, or this uri is
- * queried from a package that is not whitelisted by profile owner of the work profile via
- * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * queried from a package that is not whitelisted by profile owner of the managed profile
+ * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1896,7 +1948,7 @@ public final class CalendarContract {
Uri.parse("content://" + AUTHORITY + "/instances/searchbyday");
/**
- * The content:// style URL for querying an instance range in the work profile.
+ * The content:// style URL for querying an instance range in the managed profile.
* It supports similar semantics as {@link #CONTENT_URI}.
*
* <p>The following columns plus the columns that are whitelisted by
@@ -1916,9 +1968,9 @@ public final class CalendarContract {
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a work profile, or cross profile calendar for the work profile is disabled in
+ * of a managed profile, or cross profile calendar for the managed profile is disabled in
* Settings, or this uri is queried from a package that is not whitelisted by
- * profile owner of the work profile via
+ * profile owner of the managed profile via
* {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
@@ -1929,7 +1981,7 @@ public final class CalendarContract {
/**
* The content:// style URL for querying an instance range by Julian
- * Day in the work profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI}
+ * Day in the managed profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI}
* and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}.
*/
public static final Uri ENTERPRISE_CONTENT_BY_DAY_URI =
@@ -1937,7 +1989,7 @@ public final class CalendarContract {
/**
* The content:// style URL for querying an instance range with a search
- * term in the work profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI}
+ * term in the managed profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI}
* and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}.
*/
public static final Uri ENTERPRISE_CONTENT_SEARCH_URI =
@@ -1945,7 +1997,7 @@ public final class CalendarContract {
/**
* The content:// style URL for querying an instance range with a search
- * term in the work profile. It supports similar semantics as
+ * term in the managed profile. It supports similar semantics as
* {@link #CONTENT_SEARCH_BY_DAY_URI} and performs similar checks as
* {@link #ENTERPRISE_CONTENT_URI}.
*/
diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java
index e930f401ecd5..568ca0f6b56e 100644
--- a/core/java/android/service/carrier/CarrierIdentifier.java
+++ b/core/java/android/service/carrier/CarrierIdentifier.java
@@ -71,10 +71,8 @@ public class CarrierIdentifier implements Parcelable {
* @param gid2 group id level 2
* @param carrierid carrier unique identifier {@link TelephonyManager#getSimCarrierId()}, used
* to uniquely identify the carrier and look up the carrier configurations.
- * @param preciseCarrierId precise carrier identifier {@link TelephonyManager#getSimPreciseCarrierId()}
- * @hide
- *
- * TODO: expose this to public API
+ * @param preciseCarrierId precise carrier identifier
+ * {@link TelephonyManager#getSimPreciseCarrierId()}
*/
public CarrierIdentifier(String mcc, String mnc, @Nullable String spn,
@Nullable String imsi, @Nullable String gid1, @Nullable String gid2,
@@ -155,16 +153,16 @@ public class CarrierIdentifier implements Parcelable {
}
/**
- * Get the carrier id {@link TelephonyManager#getSimCarrierId() }
- * @hide
+ * Returns the carrier id.
+ * @see TelephonyManager#getSimCarrierId()
*/
public int getCarrierId() {
return mCarrierId;
}
/**
- * Get the precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()}
- * @hide
+ * Returns the precise carrier id.
+ * @see TelephonyManager#getSimPreciseCarrierId()
*/
public int getPreciseCarrierId() {
return mPreciseCarrierId;
diff --git a/core/java/android/view/InputEventCompatProcessor.java b/core/java/android/view/InputEventCompatProcessor.java
new file mode 100644
index 000000000000..ff8407a40cf5
--- /dev/null
+++ b/core/java/android/view/InputEventCompatProcessor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Compatibility processor for InputEvents that allows events to be adjusted before and
+ * after it is sent to the application.
+ *
+ * {@hide}
+ */
+public class InputEventCompatProcessor {
+
+ protected Context mContext;
+ protected int mTargetSdkVersion;
+
+ /** List of events to be used to return the processed events */
+ private List<InputEvent> mProcessedEvents;
+
+ public InputEventCompatProcessor(Context context) {
+ mContext = context;
+ mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ mProcessedEvents = new ArrayList<>();
+ }
+
+ /**
+ * Processes the InputEvent for compatibility before it is sent to the app, allowing for the
+ * generation of more than one event if necessary.
+ *
+ * @param e The InputEvent to process
+ * @return The list of adjusted events, or null if no adjustments are needed. Do not keep a
+ * reference to the output as the list is reused.
+ */
+ public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
+ if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
+ mProcessedEvents.clear();
+ MotionEvent motion = (MotionEvent) e;
+ final int mask =
+ MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
+ final int buttonState = motion.getButtonState();
+ final int compatButtonState = (buttonState & mask) >> 4;
+ if (compatButtonState != 0) {
+ motion.setButtonState(buttonState | compatButtonState);
+ }
+ mProcessedEvents.add(motion);
+ return mProcessedEvents;
+ }
+ return null;
+ }
+
+ /**
+ * Processes the InputEvent for compatibility before it is finished by calling
+ * InputEventReceiver#finishInputEvent().
+ *
+ * @param e The InputEvent to process
+ * @return The InputEvent to finish, or null if it should not be finished
+ */
+ public InputEvent processInputEventBeforeFinish(InputEvent e) {
+ // No changes needed
+ return e;
+ }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ab010855b896..a006e5de283e 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -36,6 +36,8 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -129,6 +131,12 @@ public class SurfaceControl implements Parcelable {
int width, int height);
private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
IBinder displayToken);
+ private static native DisplayedContentSamplingAttributes
+ nativeGetDisplayedContentSamplingAttributes(IBinder displayToken);
+ private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken,
+ boolean enable, int componentMask, int maxFrames);
+ private static native DisplayedContentSample nativeGetDisplayedContentSample(
+ IBinder displayToken, long numFrames, long timestamp);
private static native int nativeGetActiveConfig(IBinder displayToken);
private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
private static native int[] nativeGetDisplayColorModes(IBinder displayToken);
@@ -1164,6 +1172,45 @@ public class SurfaceControl implements Parcelable {
return nativeGetActiveConfig(displayToken);
}
+ /**
+ * @hide
+ */
+ public static DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+ IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetDisplayedContentSamplingAttributes(displayToken);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean setDisplayedContentSamplingEnabled(
+ IBinder displayToken, boolean enable, int componentMask, int maxFrames) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ final int maxColorComponents = 4;
+ if ((componentMask >> maxColorComponents) != 0) {
+ throw new IllegalArgumentException("invalid componentMask when enabling sampling");
+ }
+ return nativeSetDisplayedContentSamplingEnabled(
+ displayToken, enable, componentMask, maxFrames);
+ }
+
+ /**
+ * @hide
+ */
+ public static DisplayedContentSample getDisplayedContentSample(
+ IBinder displayToken, long maxFrames, long timestamp) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetDisplayedContentSample(displayToken, maxFrames, timestamp);
+ }
+
+
public static boolean setActiveConfig(IBinder displayToken, int id) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e36e2588f3d7..9fe0ddc110c2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -124,6 +124,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
+import java.util.List;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
@@ -543,6 +544,8 @@ public final class ViewRootImpl implements ViewParent,
private boolean mNeedsRendererSetup;
+ private final InputEventCompatProcessor mInputCompatProcessor;
+
/**
* Consistency verifier for debugging purposes.
*/
@@ -598,6 +601,25 @@ public final class ViewRootImpl implements ViewParent,
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ String processorOverrideName = context.getResources().getString(
+ R.string.config_inputEventCompatProcessorOverrideClassName);
+ if (processorOverrideName.isEmpty()) {
+ // No compatibility processor override, using default.
+ mInputCompatProcessor = new InputEventCompatProcessor(context);
+ } else {
+ InputEventCompatProcessor compatProcessor = null;
+ try {
+ final Class<? extends InputEventCompatProcessor> klass =
+ (Class<? extends InputEventCompatProcessor>) Class.forName(
+ processorOverrideName);
+ compatProcessor = klass.getConstructor(Context.class).newInstance(context);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create the InputEventCompatProcessor. ", e);
+ } finally {
+ mInputCompatProcessor = compatProcessor;
+ }
+ }
+
if (!sCompatibilityDone) {
sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
@@ -7166,6 +7188,7 @@ public final class ViewRootImpl implements ViewParent,
public static final int FLAG_FINISHED_HANDLED = 1 << 3;
public static final int FLAG_RESYNTHESIZED = 1 << 4;
public static final int FLAG_UNHANDLED = 1 << 5;
+ public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6;
public QueuedInputEvent mNext;
@@ -7258,7 +7281,6 @@ public final class ViewRootImpl implements ViewParent,
@UnsupportedAppUsage
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
- adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
@@ -7361,7 +7383,22 @@ public final class ViewRootImpl implements ViewParent,
if (q.mReceiver != null) {
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
- q.mReceiver.finishInputEvent(q.mEvent, handled);
+ boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0;
+ if (modified) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish");
+ InputEvent processedEvent;
+ try {
+ processedEvent =
+ mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ if (processedEvent != null) {
+ q.mReceiver.finishInputEvent(processedEvent, handled);
+ }
+ } else {
+ q.mReceiver.finishInputEvent(q.mEvent, handled);
+ }
} else {
q.mEvent.recycleIfNeededAfterDispatch();
}
@@ -7369,19 +7406,6 @@ public final class ViewRootImpl implements ViewParent,
recycleQueuedInputEvent(q);
}
- private void adjustInputEventForCompatibility(InputEvent e) {
- if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
- MotionEvent motion = (MotionEvent) e;
- final int mask =
- MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
- final int buttonState = motion.getButtonState();
- final int compatButtonState = (buttonState & mask) >> 4;
- if (compatButtonState != 0) {
- motion.setButtonState(buttonState | compatButtonState);
- }
- }
- }
-
static boolean isTerminalInputEvent(InputEvent event) {
if (event instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)event;
@@ -7452,7 +7476,28 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void onInputEvent(InputEvent event) {
- enqueueInputEvent(event, this, 0, true);
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
+ List<InputEvent> processedEvents;
+ try {
+ processedEvents =
+ mInputCompatProcessor.processInputEventForCompatibility(event);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ if (processedEvents != null) {
+ if (processedEvents.isEmpty()) {
+ // InputEvent consumed by mInputCompatProcessor
+ finishInputEvent(event, true);
+ } else {
+ for (int i = 0; i < processedEvents.size(); i++) {
+ enqueueInputEvent(
+ processedEvents.get(i), this,
+ QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
+ }
+ }
+ } else {
+ enqueueInputEvent(event, this, 0, true);
+ }
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 88b9c8096fe1..c5c1bcae232a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1004,6 +1004,36 @@ public final class AccessibilityManager {
}
/**
+ * Returns accessibility window id from window token. Accessibility window id is the one
+ * returned from AccessibilityWindowInfo.getId(). Only available for the system process.
+ *
+ * @param windowToken Window token to find accessibility window id.
+ * @return Accessibility window id for the window token.
+ * AccessibilityWindowInfo.UNDEFINED_WINDOW_ID if accessibility window id not available for
+ * the token.
+ * @hide
+ */
+ @SystemApi
+ public int getAccessibilityWindowId(IBinder windowToken) {
+ if (windowToken == null) {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+ }
+ try {
+ return service.getAccessibilityWindowId(windowToken);
+ } catch (RemoteException e) {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+ }
+
+ /**
* Sets the current state and notifies listeners, if necessary.
*
* @param stateFlags The state flags.
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 2767a82e5dba..38dac94340bb 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -76,5 +76,8 @@ interface IAccessibilityManager {
// System process only
boolean sendFingerprintGesture(int gestureKeyCode);
+ // System process only
+ int getAccessibilityWindowId(IBinder windowToken);
+
long getRecommendedTimeoutMillis();
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 1889692ea831..cc0264ac0bc9 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -31,7 +31,9 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
+import android.util.TimeUtils;
import android.view.View;
import android.view.ViewStructure;
import android.view.autofill.AutofillId;
@@ -98,6 +100,12 @@ public final class ContentCaptureManager {
*/
public static final int STATE_DISABLED = 3;
+ /**
+ * Handler message used to flush the buffer.
+ */
+ private static final int MSG_FLUSH = 1;
+
+
private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
/**
@@ -106,6 +114,12 @@ public final class ContentCaptureManager {
// TODO(b/111276913): use settings
private static final int MAX_BUFFER_SIZE = 100;
+ /**
+ * Frequency the buffer is flushed if stale.
+ */
+ // TODO(b/111276913): use settings
+ private static final int FLUSHING_FREQUENCY_MS = 5_000;
+
@NonNull
private final AtomicBoolean mDisabled = new AtomicBoolean();
@@ -136,6 +150,9 @@ public final class ContentCaptureManager {
// held at the Application level
private final Handler mHandler;
+ // Used just for debugging purposes (on dump)
+ private long mNextFlush;
+
/** @hide */
public ContentCaptureManager(@NonNull Context context,
@Nullable IContentCaptureManager service) {
@@ -207,9 +224,17 @@ public final class ContentCaptureManager {
mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
}
mEvents.add(event);
+
final int numberEvents = mEvents.size();
- if (numberEvents < MAX_BUFFER_SIZE && !forceFlush) {
- // Buffering events, return right away...
+
+ // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are
+ // buffered (either total or per autofillid). For
+ // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer
+ // "a" and "b" then send "abc".
+ final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
+
+ if (bufferEvent && !forceFlush) {
+ handleScheduleFlush();
return;
}
@@ -236,10 +261,38 @@ public final class ContentCaptureManager {
return;
}
+ handleForceFlush();
+ }
+
+ private void handleScheduleFlush() {
+ if (mHandler.hasMessages(MSG_FLUSH)) {
+ // "Renew" the flush message by removing the previous one
+ mHandler.removeMessages(MSG_FLUSH);
+ }
+ mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
+ if (VERBOSE) {
+ Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
+ }
+ mHandler.sendMessageDelayed(
+ obtainMessage(ContentCaptureManager::handleFlushIfNeeded, this).setWhat(MSG_FLUSH),
+ FLUSHING_FREQUENCY_MS);
+ }
+
+ private void handleFlushIfNeeded() {
+ if (mEvents.isEmpty()) {
+ if (VERBOSE) Log.v(TAG, "Nothing to flush");
+ return;
+ }
+ handleForceFlush();
+ }
+
+ private void handleForceFlush() {
+ final int numberEvents = mEvents.size();
try {
if (DEBUG) {
Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
}
+ mHandler.removeMessages(MSG_FLUSH);
mService.sendEvents(mContext.getUserId(), mId, mEvents);
// TODO(b/111276913): decide whether we should clear or set it to null, as each has
// its own advantages: clearing will save extra allocations while the session is
@@ -307,6 +360,7 @@ public final class ContentCaptureManager {
mApplicationToken = null;
mComponentName = null;
mEvents = null;
+ mHandler.removeMessages(MSG_FLUSH);
}
/**
@@ -443,7 +497,7 @@ public final class ContentCaptureManager {
pw.print(prefix2); pw.print("component name: ");
pw.println(mComponentName.flattenToShortString());
}
- if (mEvents != null) {
+ if (mEvents != null && !mEvents.isEmpty()) {
final int numberEvents = mEvents.size();
pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents);
pw.print('/'); pw.println(MAX_BUFFER_SIZE);
@@ -455,6 +509,9 @@ public final class ContentCaptureManager {
pw.println();
}
}
+ pw.print(prefix2); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
+ pw.print(prefix2); pw.print("next flush: ");
+ TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
}
}
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index 797b861e9e70..b41096c74bf7 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -16,7 +16,6 @@
package android.view.textclassifier;
-import android.annotation.NonNull;
import android.app.Person;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -30,6 +29,7 @@ import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -57,9 +57,9 @@ public final class ActionsSuggestionsHelper {
* </ul>
* User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0.
*/
- @NonNull
public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages(
- @NonNull List<ConversationActions.Message> messages) {
+ List<ConversationActions.Message> messages,
+ Function<CharSequence, String> languageDetector) {
List<ConversationActions.Message> messagesWithText =
messages.stream()
.filter(message -> !TextUtils.isEmpty(message.getText()))
@@ -67,31 +67,18 @@ public final class ActionsSuggestionsHelper {
if (messagesWithText.isEmpty()) {
return new ActionsSuggestionsModel.ConversationMessage[0];
}
- int size = messagesWithText.size();
- // If the last message (the most important one) does not have the Person object, we will
- // just use the last message and consider this message is sent from a remote user.
- ConversationActions.Message lastMessage = messages.get(size - 1);
- boolean useLastMessageOnly = lastMessage.getAuthor() == null;
- if (useLastMessageOnly) {
- return new ActionsSuggestionsModel.ConversationMessage[]{
- new ActionsSuggestionsModel.ConversationMessage(
- FIRST_NON_LOCAL_USER,
- lastMessage.getText().toString(),
- 0,
- null)};
- }
-
- // Encode the messages in the reverse order, stop whenever the Person object is missing.
Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>();
PersonEncoder personEncoder = new PersonEncoder();
+ int size = messagesWithText.size();
for (int i = size - 1; i >= 0; i--) {
ConversationActions.Message message = messagesWithText.get(i);
- if (message.getAuthor() == null) {
- break;
- }
+ long referenceTime = message.getReferenceTime() == null
+ ? 0
+ : message.getReferenceTime().toInstant().toEpochMilli();
nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage(
personEncoder.encode(message.getAuthor()),
- message.getText().toString(), 0, null));
+ message.getText().toString(), referenceTime,
+ languageDetector.apply(message.getText())));
}
return nativeMessages.toArray(
new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]);
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 04b94b0e03ad..04924c9a59c4 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -349,17 +349,31 @@ public final class ConversationActions implements Parcelable {
/**
* Represents the local user.
*
- * @see Builder#setAuthor(Person)
+ * @see Builder#Builder(Person)
*/
public static final Person PERSON_USER_LOCAL =
new Person.Builder()
.setKey("text-classifier-conversation-actions-local-user")
.build();
+ /**
+ * Represents the remote user.
+ * <p>
+ * If possible, you are suggested to create a {@link Person} object that can identify
+ * the remote user better, so that the underlying model could differentiate between
+ * different remote users.
+ *
+ * @see Builder#Builder(Person)
+ */
+ public static final Person PERSON_USER_REMOTE =
+ new Person.Builder()
+ .setKey("text-classifier-conversation-actions-remote-user")
+ .build();
+
@Nullable
private final Person mAuthor;
@Nullable
- private final ZonedDateTime mComposeTime;
+ private final ZonedDateTime mReferenceTime;
@Nullable
private final CharSequence mText;
@NonNull
@@ -367,18 +381,18 @@ public final class ConversationActions implements Parcelable {
private Message(
@Nullable Person author,
- @Nullable ZonedDateTime composeTime,
+ @Nullable ZonedDateTime referenceTime,
@Nullable CharSequence text,
@NonNull Bundle bundle) {
mAuthor = author;
- mComposeTime = composeTime;
+ mReferenceTime = referenceTime;
mText = text;
mExtras = Preconditions.checkNotNull(bundle);
}
private Message(Parcel in) {
mAuthor = in.readParcelable(null);
- mComposeTime =
+ mReferenceTime =
in.readInt() == 0
? null
: ZonedDateTime.parse(
@@ -390,9 +404,9 @@ public final class ConversationActions implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mAuthor, flags);
- parcel.writeInt(mComposeTime != null ? 1 : 0);
- if (mComposeTime != null) {
- parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+ parcel.writeInt(mReferenceTime != null ? 1 : 0);
+ if (mReferenceTime != null) {
+ parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
parcel.writeCharSequence(mText);
parcel.writeBundle(mExtras);
@@ -417,15 +431,18 @@ public final class ConversationActions implements Parcelable {
};
/** Returns the person that composed the message. */
- @Nullable
+ @NonNull
public Person getAuthor() {
return mAuthor;
}
- /** Returns the compose time of the message. */
+ /**
+ * Returns the reference time of the message, for example it could be the compose or send
+ * time of this message.
+ */
@Nullable
- public ZonedDateTime getTime() {
- return mComposeTime;
+ public ZonedDateTime getReferenceTime() {
+ return mReferenceTime;
}
/** Returns the text of the message. */
@@ -451,34 +468,38 @@ public final class ConversationActions implements Parcelable {
@Nullable
private Person mAuthor;
@Nullable
- private ZonedDateTime mComposeTime;
+ private ZonedDateTime mReferenceTime;
@Nullable
private CharSequence mText;
@Nullable
private Bundle mExtras;
/**
- * Sets the person who composed this message.
- * <p>
- * Use {@link #PERSON_USER_LOCAL} to represent the local user.
+ * Constructs a builder.
+ *
+ * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL}
+ * to represent the local user. If it is not possible to identify the
+ * remote user that the local user is conversing with, use
+ * {@link #PERSON_USER_REMOTE} to represent a remote user.
*/
- @NonNull
- public Builder setAuthor(@Nullable Person author) {
- mAuthor = author;
- return this;
+ public Builder(@NonNull Person author) {
+ mAuthor = Preconditions.checkNotNull(author);
}
- /** Sets the text of this message */
+ /** Sets the text of this message. */
@NonNull
public Builder setText(@Nullable CharSequence text) {
mText = text;
return this;
}
- /** Sets the compose time of this message */
+ /**
+ * Sets the reference time of this message, for example it could be the compose or send
+ * time of this message.
+ */
@NonNull
- public Builder setComposeTime(@Nullable ZonedDateTime composeTime) {
- mComposeTime = composeTime;
+ public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+ mReferenceTime = referenceTime;
return this;
}
@@ -494,7 +515,7 @@ public final class ConversationActions implements Parcelable {
public Message build() {
return new Message(
mAuthor,
- mComposeTime,
+ mReferenceTime,
mText == null ? null : new SpannedString(mText),
mExtras == null ? new Bundle() : mExtras.deepCopy());
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index a2536cbd911c..ea82bf3f3846 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -162,6 +162,14 @@ public interface TextClassifier {
TextClassifier NO_OP = new TextClassifier() {};
/**
+ * Used as a boolean value to indicate the intent is generated by TextClassifier.
+ * <p>
+ * All {@link TextClassifier} implementations should set this boolean extra to be true in their
+ * generated intents.
+ */
+ String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
+
+ /**
* Returns suggested text selection start and end indices, recognized entity types, and their
* associated confidence scores. The entity types are ordered from highest to lowest scoring.
*
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 8e14dfdb7ee3..deda92646e71 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -139,7 +139,7 @@ public final class TextClassifierImpl implements TextClassifier {
FACTORY_MODEL_DIR,
LANG_ID_FACTORY_MODEL_FILENAME_REGEX,
UPDATED_LANG_ID_MODEL_FILE,
- fd -> -1, // TODO: Replace this with LangIdModel.getVersion(fd)
+ LangIdModel::getVersion,
fd -> ModelFileManager.ModelFile.LANGUAGE_INDEPENDENT));
mActionsModelFileManager = new ModelFileManager(
new ModelFileManager.ModelFileSupplierImpl(
@@ -374,7 +374,8 @@ public final class TextClassifierImpl implements TextClassifier {
return mFallback.suggestConversationActions(request);
}
ActionsSuggestionsModel.ConversationMessage[] nativeMessages =
- ActionsSuggestionsHelper.toNativeMessages(request.getConversation());
+ ActionsSuggestionsHelper.toNativeMessages(request.getConversation(),
+ this::detectLanguageTagsFromText);
if (nativeMessages.length == 0) {
return mFallback.suggestConversationActions(request);
}
@@ -407,6 +408,26 @@ public final class TextClassifierImpl implements TextClassifier {
return mFallback.suggestConversationActions(request);
}
+ @Nullable
+ private String detectLanguageTagsFromText(CharSequence text) {
+ TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
+ TextLanguage textLanguage = detectLanguage(request);
+ int localeHypothesisCount = textLanguage.getLocaleHypothesisCount();
+ List<String> languageTags = new ArrayList<>();
+ // TODO: Reconsider this and probably make the score threshold configurable.
+ for (int i = 0; i < localeHypothesisCount; i++) {
+ ULocale locale = textLanguage.getLocale(i);
+ if (textLanguage.getConfidenceScore(locale) < 0.5) {
+ break;
+ }
+ languageTags.add(locale.toLanguageTag());
+ }
+ if (languageTags.isEmpty()) {
+ return LocaleList.getDefault().toLanguageTags();
+ }
+ return String.join(",", languageTags);
+ }
+
private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) {
List<String> defaultActionTypes =
request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION)
@@ -777,6 +798,9 @@ public final class TextClassifierImpl implements TextClassifier {
if (foreignText) {
insertTranslateAction(actions, context, text);
}
+ actions.forEach(
+ action -> action.getIntent()
+ .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
return actions;
}
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 3842f6659704..4da339165655 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -445,8 +445,18 @@ public final class AssociationState {
}
}
+ public boolean hasProcess(String procName) {
+ final int NSRC = mSources.size();
+ for (int isrc = 0; isrc < NSRC; isrc++) {
+ if (mSources.keyAt(isrc).mProcess.equals(procName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
- long now, long totalTime, boolean dumpDetails, boolean dumpAll) {
+ long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) {
if (dumpAll) {
pw.print(prefix);
pw.print("mNumActive=");
@@ -456,6 +466,9 @@ public final class AssociationState {
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceKey key = mSources.keyAt(isrc);
final SourceState src = mSources.valueAt(isrc);
+ if (reqPackage != null && !reqPackage.equals(key.mProcess)) {
+ continue;
+ }
pw.print(prefixInner);
pw.print("<- ");
pw.print(key.mProcess);
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 19d8a836fc4c..9ee583a97b8d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -1396,10 +1396,10 @@ public final class ProcessStats implements Parcelable {
return as;
}
- // See b/118826162 -- to avoid logspaming, we rate limit the WTF.
- private static final long INVERSE_PROC_STATE_WTF_MIN_INTERVAL_MS = 10_000L;
- private long mNextInverseProcStateWtfUptime;
- private int mSkippedInverseProcStateWtfCount;
+ // See b/118826162 -- to avoid logspaming, we rate limit the warnings.
+ private static final long INVERSE_PROC_STATE_WARNING_MIN_INTERVAL_MS = 10_000L;
+ private long mNextInverseProcStateWarningUptime;
+ private int mSkippedInverseProcStateWarningCount;
public void updateTrackingAssociationsLocked(int curSeq, long now) {
final int NUM = mTrackingAssociations.size();
@@ -1423,18 +1423,19 @@ public final class ProcessStats implements Parcelable {
act.stopActive(now);
if (act.mProcState < procState) {
final long nowUptime = SystemClock.uptimeMillis();
- if (mNextInverseProcStateWtfUptime > nowUptime) {
- mSkippedInverseProcStateWtfCount++;
+ if (mNextInverseProcStateWarningUptime > nowUptime) {
+ mSkippedInverseProcStateWarningCount++;
} else {
// TODO We still see it during boot related to GMS-core.
// b/118826162
- Slog.wtf(TAG, "Tracking association " + act + " whose proc state "
+ Slog.w(TAG, "Tracking association " + act + " whose proc state "
+ act.mProcState + " is better than process " + proc
+ " proc state " + procState
- + " (" + mSkippedInverseProcStateWtfCount + " skipped)");
- mSkippedInverseProcStateWtfCount = 0;
- mNextInverseProcStateWtfUptime =
- nowUptime + INVERSE_PROC_STATE_WTF_MIN_INTERVAL_MS;
+ + " (" + mSkippedInverseProcStateWarningCount
+ + " skipped)");
+ mSkippedInverseProcStateWarningCount = 0;
+ mNextInverseProcStateWarningUptime =
+ nowUptime + INVERSE_PROC_STATE_WARNING_MIN_INTERVAL_MS;
}
}
}
@@ -1474,6 +1475,7 @@ public final class ProcessStats implements Parcelable {
final int NSRVS = pkgState.mServices.size();
final int NASCS = pkgState.mAssociations.size();
final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ boolean onlyAssociations = false;
if (!pkgMatch) {
boolean procMatch = false;
for (int iproc = 0; iproc < NPROCS; iproc++) {
@@ -1484,7 +1486,18 @@ public final class ProcessStats implements Parcelable {
}
}
if (!procMatch) {
- continue;
+ // Check if this app has any associations with the requested
+ // package, so that if so we print those.
+ for (int iasc = 0; iasc < NASCS; iasc++) {
+ AssociationState asc = pkgState.mAssociations.valueAt(iasc);
+ if (asc.hasProcess(reqPackage)) {
+ onlyAssociations = true;
+ break;
+ }
+ }
+ if (!onlyAssociations) {
+ continue;
+ }
}
}
if (NPROCS > 0 || NSRVS > 0 || NASCS > 0) {
@@ -1502,7 +1515,7 @@ public final class ProcessStats implements Parcelable {
pw.print(vers);
pw.println(":");
}
- if ((section & REPORT_PKG_PROC_STATS) != 0) {
+ if ((section & REPORT_PKG_PROC_STATS) != 0 && !onlyAssociations) {
if (!dumpSummary || dumpAll) {
for (int iproc = 0; iproc < NPROCS; iproc++) {
ProcessState proc = pkgState.mProcesses.valueAt(iproc);
@@ -1549,7 +1562,7 @@ public final class ProcessStats implements Parcelable {
now, totalTime);
}
}
- if ((section & REPORT_PKG_SVC_STATS) != 0) {
+ if ((section & REPORT_PKG_SVC_STATS) != 0 && !onlyAssociations) {
for (int isvc = 0; isvc < NSRVS; isvc++) {
ServiceState svc = pkgState.mServices.valueAt(isvc);
if (!pkgMatch && !reqPackage.equals(svc.getProcessName())) {
@@ -1578,7 +1591,9 @@ public final class ProcessStats implements Parcelable {
for (int iasc = 0; iasc < NASCS; iasc++) {
AssociationState asc = pkgState.mAssociations.valueAt(iasc);
if (!pkgMatch && !reqPackage.equals(asc.getProcessName())) {
- continue;
+ if (!onlyAssociations || !asc.hasProcess(reqPackage)) {
+ continue;
+ }
}
if (activeOnly && !asc.isInUse()) {
pw.print(" (Not active association: ");
@@ -1596,7 +1611,8 @@ public final class ProcessStats implements Parcelable {
pw.print(" Process: ");
pw.println(asc.getProcessName());
asc.dumpStats(pw, " ", " ", " ",
- now, totalTime, dumpDetails, dumpAll);
+ now, totalTime, onlyAssociations ? reqPackage : null,
+ dumpDetails, dumpAll);
}
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 65213c0a1085..65b9fad97d89 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -81,6 +81,11 @@ public final class Zygote {
public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ;
/** Read-write external storage should be mounted. */
public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE;
+ /**
+ * Mount mode for package installers which should give them access to
+ * all obb dirs in addition to their package sandboxes
+ */
+ public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
/** Read-write external storage should be mounted instead of package sandbox */
public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4a94ec4a4071..f182c4d447df 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -656,7 +656,9 @@ class ZygoteConnection {
mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
} else if (arg.equals("--mount-external-full")) {
mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
- } else if (arg.equals("--query-abi-list")) {
+ } else if (arg.equals("--mount-external-installer")) {
+ mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else if (arg.equals("--query-abi-list")) {
abiListQuery = true;
} else if (arg.equals("--get-pid")) {
pidQuery = true;
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index b00e6fdd5e9a..841e5b679f5f 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.storage.StorageManager;
import android.permission.PermissionManager.SplitPermissionInfo;
import android.text.TextUtils;
@@ -66,8 +67,12 @@ public class SystemConfig {
private static final int ALLOW_PRIVAPP_PERMISSIONS = 0x10;
private static final int ALLOW_OEM_PERMISSIONS = 0x20;
private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x40;
+ private static final int ALLOW_ASSOCIATIONS = 0x80;
private static final int ALLOW_ALL = ~0;
+ // property for runtime configuration differentiation
+ private static final String SKU_PROPERTY = "ro.boot.product.hardware.sku";
+
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
int[] mGlobalGids;
@@ -191,6 +196,12 @@ public class SystemConfig {
final ArrayMap<String, ArrayMap<String, Boolean>> mOemPermissions = new ArrayMap<>();
+ // Allowed associations between applications. If there are any entries
+ // for an app, those are the only associations allowed; otherwise, all associations
+ // are allowed. Allowing an association from app A to app B means app A can not
+ // associate with any other apps, but does not limit what apps B can associate with.
+ final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>();
+
public static SystemConfig getInstance() {
synchronized (SystemConfig.class) {
if (sInstance == null) {
@@ -316,6 +327,10 @@ public class SystemConfig {
return Collections.emptyMap();
}
+ public ArrayMap<String, ArraySet<String>> getAllowedAssociations() {
+ return mAllowedAssociations;
+ }
+
SystemConfig() {
// Read configuration from system
readPermissions(Environment.buildPath(
@@ -325,8 +340,9 @@ public class SystemConfig {
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
- // Vendors are only allowed to customze libs, features and privapp permissions
- int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS;
+ // Vendors are only allowed to customize these
+ int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
+ | ALLOW_ASSOCIATIONS;
if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) {
// For backward compatibility
vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
@@ -344,8 +360,19 @@ public class SystemConfig {
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
- // Allow OEM to customize features and OEM permissions
- int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS;
+ String skuProperty = SystemProperties.get(SKU_PROPERTY, "");
+ if (!skuProperty.isEmpty()) {
+ String skuDir = "sku_" + skuProperty;
+
+ readPermissions(Environment.buildPath(
+ Environment.getOdmDirectory(), "etc", "sysconfig", skuDir), odmPermissionFlag);
+ readPermissions(Environment.buildPath(
+ Environment.getOdmDirectory(), "etc", "permissions", skuDir),
+ odmPermissionFlag);
+ }
+
+ // Allow OEM to customize these
+ int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS | ALLOW_ASSOCIATIONS;
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag);
readPermissions(Environment.buildPath(
@@ -380,6 +407,10 @@ public class SystemConfig {
// Iterate over the files in the directory and scan .xml files
File platformFile = null;
for (File f : libraryDir.listFiles()) {
+ if (!f.isFile()) {
+ continue;
+ }
+
// We'll read platform.xml last
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
platformFile = f;
@@ -404,6 +435,11 @@ public class SystemConfig {
}
}
+ private void logNotAllowedInPartition(String name, File permFile, XmlPullParser parser) {
+ Slog.w(TAG, "<" + name + "> not allowed in partition of "
+ + permFile + " at " + parser.getPositionDescription());
+ }
+
private void readPermissionsFromXml(File permFile, int permissionFlag) {
FileReader permReader = null;
try {
@@ -434,14 +470,17 @@ public class SystemConfig {
+ ": found " + parser.getName() + ", expected 'permissions' or 'config'");
}
- boolean allowAll = permissionFlag == ALLOW_ALL;
- boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
- boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
- boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
- boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
- boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0;
- boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
- boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0;
+ final boolean allowAll = permissionFlag == ALLOW_ALL;
+ final boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
+ final boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
+ final boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
+ final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
+ final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
+ != 0;
+ final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
+ final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
+ != 0;
+ final boolean allowAssociations = (permissionFlag & ALLOW_ASSOCIATIONS) != 0;
while (true) {
XmlUtils.nextElement(parser);
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
@@ -449,297 +488,425 @@ public class SystemConfig {
}
String name = parser.getName();
- if ("group".equals(name) && allowAll) {
- String gidStr = parser.getAttributeValue(null, "gid");
- if (gidStr != null) {
- int gid = android.os.Process.getGidForName(gidStr);
- mGlobalGids = appendInt(mGlobalGids, gid);
- } else {
- Slog.w(TAG, "<group> without gid in " + permFile + " at "
- + parser.getPositionDescription());
- }
-
+ if (name == null) {
XmlUtils.skipCurrentTag(parser);
continue;
- } else if ("permission".equals(name) && allowPermissions) {
- String perm = parser.getAttributeValue(null, "name");
- if (perm == null) {
- Slog.w(TAG, "<permission> without name in " + permFile + " at "
- + parser.getPositionDescription());
+ }
+ switch (name) {
+ case "group": {
+ if (allowAll) {
+ String gidStr = parser.getAttributeValue(null, "gid");
+ if (gidStr != null) {
+ int gid = android.os.Process.getGidForName(gidStr);
+ mGlobalGids = appendInt(mGlobalGids, gid);
+ } else {
+ Slog.w(TAG, "<" + name + "> without gid in " + permFile + " at "
+ + parser.getPositionDescription());
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
XmlUtils.skipCurrentTag(parser);
- continue;
- }
- perm = perm.intern();
- readPermission(parser, perm);
-
- } else if ("assign-permission".equals(name) && allowPermissions) {
- String perm = parser.getAttributeValue(null, "name");
- if (perm == null) {
- Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
- + parser.getPositionDescription());
+ } break;
+ case "permission": {
+ if (allowPermissions) {
+ String perm = parser.getAttributeValue(null, "name");
+ if (perm == null) {
+ Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ break;
+ }
+ perm = perm.intern();
+ readPermission(parser, perm);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } break;
+ case "assign-permission": {
+ if (allowPermissions) {
+ String perm = parser.getAttributeValue(null, "name");
+ if (perm == null) {
+ Slog.w(TAG, "<" + name + "> without name in " + permFile
+ + " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ break;
+ }
+ String uidStr = parser.getAttributeValue(null, "uid");
+ if (uidStr == null) {
+ Slog.w(TAG, "<" + name + "> without uid in " + permFile
+ + " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ break;
+ }
+ int uid = Process.getUidForName(uidStr);
+ if (uid < 0) {
+ Slog.w(TAG, "<" + name + "> with unknown uid \""
+ + uidStr + " in " + permFile + " at "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ break;
+ }
+ perm = perm.intern();
+ ArraySet<String> perms = mSystemPermissions.get(uid);
+ if (perms == null) {
+ perms = new ArraySet<String>();
+ mSystemPermissions.put(uid, perms);
+ }
+ perms.add(perm);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String uidStr = parser.getAttributeValue(null, "uid");
- if (uidStr == null) {
- Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
- + parser.getPositionDescription());
+ } break;
+ case "split-permission": {
+ if (allowPermissions) {
+ readSplitPermission(parser, permFile);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } break;
+ case "library": {
+ if (allowLibs) {
+ String lname = parser.getAttributeValue(null, "name");
+ String lfile = parser.getAttributeValue(null, "file");
+ String ldependency = parser.getAttributeValue(null, "dependency");
+ if (lname == null) {
+ Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+ + parser.getPositionDescription());
+ } else if (lfile == null) {
+ Slog.w(TAG, "<" + name + "> without file in " + permFile + " at "
+ + parser.getPositionDescription());
+ } else {
+ //Log.i(TAG, "Got library " + lname + " in " + lfile);
+ SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
+ ldependency == null ? new String[0] : ldependency.split(":"));
+ mSharedLibraries.put(lname, entry);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
XmlUtils.skipCurrentTag(parser);
- continue;
- }
- int uid = Process.getUidForName(uidStr);
- if (uid < 0) {
- Slog.w(TAG, "<assign-permission> with unknown uid \""
- + uidStr + " in " + permFile + " at "
- + parser.getPositionDescription());
+ } break;
+ case "feature": {
+ if (allowFeatures) {
+ String fname = parser.getAttributeValue(null, "name");
+ int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
+ boolean allowed;
+ if (!lowRam) {
+ allowed = true;
+ } else {
+ String notLowRam = parser.getAttributeValue(null, "notLowRam");
+ allowed = !"true".equals(notLowRam);
+ }
+ if (fname == null) {
+ Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+ + parser.getPositionDescription());
+ } else if (allowed) {
+ addFeature(fname, fversion);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
XmlUtils.skipCurrentTag(parser);
- continue;
- }
- perm = perm.intern();
- ArraySet<String> perms = mSystemPermissions.get(uid);
- if (perms == null) {
- perms = new ArraySet<String>();
- mSystemPermissions.put(uid, perms);
- }
- perms.add(perm);
- XmlUtils.skipCurrentTag(parser);
-
- } else if ("split-permission".equals(name) && allowPermissions) {
- readSplitPermission(parser, permFile);
- } else if ("library".equals(name) && allowLibs) {
- String lname = parser.getAttributeValue(null, "name");
- String lfile = parser.getAttributeValue(null, "file");
- String ldependency = parser.getAttributeValue(null, "dependency");
- if (lname == null) {
- Slog.w(TAG, "<library> without name in " + permFile + " at "
- + parser.getPositionDescription());
- } else if (lfile == null) {
- Slog.w(TAG, "<library> without file in " + permFile + " at "
- + parser.getPositionDescription());
- } else {
- //Log.i(TAG, "Got library " + lname + " in " + lfile);
- SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
- ldependency == null ? new String[0] : ldependency.split(":"));
- mSharedLibraries.put(lname, entry);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
- } else if ("feature".equals(name) && allowFeatures) {
- String fname = parser.getAttributeValue(null, "name");
- int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
- boolean allowed;
- if (!lowRam) {
- allowed = true;
- } else {
- String notLowRam = parser.getAttributeValue(null, "notLowRam");
- allowed = !"true".equals(notLowRam);
- }
- if (fname == null) {
- Slog.w(TAG, "<feature> without name in " + permFile + " at "
- + parser.getPositionDescription());
- } else if (allowed) {
- addFeature(fname, fversion);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("unavailable-feature".equals(name) && allowFeatures) {
- String fname = parser.getAttributeValue(null, "name");
- if (fname == null) {
- Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
- + parser.getPositionDescription());
- } else {
- mUnavailableFeatures.add(fname);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
- + permFile + " at " + parser.getPositionDescription());
- } else {
- mAllowInPowerSaveExceptIdle.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("allow-in-power-save".equals(name) && allowAll) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
- + parser.getPositionDescription());
- } else {
- mAllowInPowerSave.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("allow-in-data-usage-save".equals(name) && allowAll) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<allow-in-data-usage-save> without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mAllowInDataUsageSave.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("allow-unthrottled-location".equals(name) && allowAll) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<allow-unthrottled-location> without package in "
- + permFile + " at " + parser.getPositionDescription());
- } else {
- mAllowUnthrottledLocation.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("allow-implicit-broadcast".equals(name) && allowAll) {
- String action = parser.getAttributeValue(null, "action");
- if (action == null) {
- Slog.w(TAG, "<allow-implicit-broadcast> without action in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mAllowImplicitBroadcasts.add(action);
- }
- XmlUtils.skipCurrentTag(parser);
- continue;
-
- } else if ("app-link".equals(name) && allowAppConfigs) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<app-link> without package in " + permFile + " at "
- + parser.getPositionDescription());
- } else {
- mLinkedApps.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mSystemUserWhitelistedApps.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mSystemUserBlacklistedApps.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
- String pkgname = parser.getAttributeValue(null, "package");
- String clsname = parser.getAttributeValue(null, "class");
- if (pkgname == null) {
- Slog.w(TAG, "<default-enabled-vr-app without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else if (clsname == null) {
- Slog.w(TAG, "<default-enabled-vr-app without class in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
- String serviceName = parser.getAttributeValue(null, "service");
- if (serviceName == null) {
- Slog.w(TAG, "<backup-transport-whitelisted-service> without service in "
- + permFile + " at " + parser.getPositionDescription());
- } else {
- ComponentName cn = ComponentName.unflattenFromString(serviceName);
- if (cn == null) {
- Slog.w(TAG,
- "<backup-transport-whitelisted-service> with invalid service name "
- + serviceName + " in "+ permFile
- + " at " + parser.getPositionDescription());
+ } break;
+ case "unavailable-feature": {
+ if (allowFeatures) {
+ String fname = parser.getAttributeValue(null, "name");
+ if (fname == null) {
+ Slog.w(TAG, "<" + name + "> without name in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mUnavailableFeatures.add(fname);
+ }
} else {
- mBackupTransportWhitelist.add(cn);
+ logNotAllowedInPartition(name, permFile, parser);
}
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
- && allowAppConfigs) {
- String pkgname = parser.getAttributeValue(null, "package");
- String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage");
- if (pkgname == null || carrierPkgname == null) {
- Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app"
- + " without package or carrierAppPackage in " + permFile + " at "
- + parser.getPositionDescription());
- } else {
- List<String> associatedPkgs =
- mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
- carrierPkgname);
- if (associatedPkgs == null) {
- associatedPkgs = new ArrayList<>();
- mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
- carrierPkgname, associatedPkgs);
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "allow-in-power-save-except-idle": {
+ if (allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowInPowerSaveExceptIdle.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
}
- associatedPkgs.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("disabled-until-used-preinstalled-carrier-app".equals(name)
- && allowAppConfigs) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG,
- "<disabled-until-used-preinstalled-carrier-app> without "
- + "package in " + permFile + " at "
- + parser.getPositionDescription());
- } else {
- mDisabledUntilUsedPreinstalledCarrierApps.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } else if ("privapp-permissions".equals(name) && allowPrivappPermissions) {
- // privapp permissions from system, vendor, product and product_services
- // partitions are stored separately. This is to prevent xml files in the vendor
- // partition from granting permissions to priv apps in the system partition and
- // vice versa.
- boolean vendor = permFile.toPath().startsWith(
- Environment.getVendorDirectory().toPath() + "/")
- || permFile.toPath().startsWith(
- Environment.getOdmDirectory().toPath() + "/");
- boolean product = permFile.toPath().startsWith(
- Environment.getProductDirectory().toPath() + "/");
- boolean productServices = permFile.toPath().startsWith(
- Environment.getProductServicesDirectory().toPath() + "/");
- if (vendor) {
- readPrivAppPermissions(parser, mVendorPrivAppPermissions,
- mVendorPrivAppDenyPermissions);
- } else if (product) {
- readPrivAppPermissions(parser, mProductPrivAppPermissions,
- mProductPrivAppDenyPermissions);
- } else if (productServices) {
- readPrivAppPermissions(parser, mProductServicesPrivAppPermissions,
- mProductServicesPrivAppDenyPermissions);
- } else {
- readPrivAppPermissions(parser, mPrivAppPermissions,
- mPrivAppDenyPermissions);
- }
- } else if ("oem-permissions".equals(name) && allowOemPermissions) {
- readOemPermissions(parser);
- } else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<hidden-api-whitelisted-app> without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mHiddenApiPackageWhitelist.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } else {
- Slog.w(TAG, "Tag " + name + " is unknown or not allowed in "
- + permFile.getParent());
- XmlUtils.skipCurrentTag(parser);
- continue;
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "allow-in-power-save": {
+ if (allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowInPowerSave.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "allow-in-data-usage-save": {
+ if (allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowInDataUsageSave.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "allow-unthrottled-location": {
+ if (allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowUnthrottledLocation.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "allow-implicit-broadcast": {
+ if (allowAll) {
+ String action = parser.getAttributeValue(null, "action");
+ if (action == null) {
+ Slog.w(TAG, "<" + name + "> without action in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowImplicitBroadcasts.add(action);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "app-link": {
+ if (allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mLinkedApps.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "system-user-whitelisted-app": {
+ if (allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mSystemUserWhitelistedApps.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "system-user-blacklisted-app": {
+ if (allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mSystemUserBlacklistedApps.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "default-enabled-vr-app": {
+ if (allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ String clsname = parser.getAttributeValue(null, "class");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else if (clsname == null) {
+ Slog.w(TAG, "<" + name + "> without class in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "backup-transport-whitelisted-service": {
+ if (allowFeatures) {
+ String serviceName = parser.getAttributeValue(null, "service");
+ if (serviceName == null) {
+ Slog.w(TAG, "<" + name + "> without service in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ ComponentName cn = ComponentName.unflattenFromString(serviceName);
+ if (cn == null) {
+ Slog.w(TAG, "<" + name + "> with invalid service name "
+ + serviceName + " in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mBackupTransportWhitelist.add(cn);
+ }
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "disabled-until-used-preinstalled-carrier-associated-app": {
+ if (allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ String carrierPkgname = parser.getAttributeValue(null,
+ "carrierAppPackage");
+ if (pkgname == null || carrierPkgname == null) {
+ Slog.w(TAG, "<" + name
+ + "> without package or carrierAppPackage in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ List<String> associatedPkgs =
+ mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
+ carrierPkgname);
+ if (associatedPkgs == null) {
+ associatedPkgs = new ArrayList<>();
+ mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
+ carrierPkgname, associatedPkgs);
+ }
+ associatedPkgs.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "disabled-until-used-preinstalled-carrier-app": {
+ if (allowAppConfigs) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG,
+ "<" + name + "> without "
+ + "package in " + permFile + " at "
+ + parser.getPositionDescription());
+ } else {
+ mDisabledUntilUsedPreinstalledCarrierApps.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "privapp-permissions": {
+ if (allowPrivappPermissions) {
+ // privapp permissions from system, vendor, product and product_services
+ // partitions are stored separately. This is to prevent xml files in
+ // the vendor partition from granting permissions to priv apps in the
+ // system partition and vice versa.
+ boolean vendor = permFile.toPath().startsWith(
+ Environment.getVendorDirectory().toPath() + "/")
+ || permFile.toPath().startsWith(
+ Environment.getOdmDirectory().toPath() + "/");
+ boolean product = permFile.toPath().startsWith(
+ Environment.getProductDirectory().toPath() + "/");
+ boolean productServices = permFile.toPath().startsWith(
+ Environment.getProductServicesDirectory().toPath() + "/");
+ if (vendor) {
+ readPrivAppPermissions(parser, mVendorPrivAppPermissions,
+ mVendorPrivAppDenyPermissions);
+ } else if (product) {
+ readPrivAppPermissions(parser, mProductPrivAppPermissions,
+ mProductPrivAppDenyPermissions);
+ } else if (productServices) {
+ readPrivAppPermissions(parser, mProductServicesPrivAppPermissions,
+ mProductServicesPrivAppDenyPermissions);
+ } else {
+ readPrivAppPermissions(parser, mPrivAppPermissions,
+ mPrivAppDenyPermissions);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } break;
+ case "oem-permissions": {
+ if (allowOemPermissions) {
+ readOemPermissions(parser);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } break;
+ case "hidden-api-whitelisted-app": {
+ if (allowApiWhitelisting) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mHiddenApiPackageWhitelist.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ case "allow-association": {
+ if (allowAssociations) {
+ String target = parser.getAttributeValue(null, "target");
+ if (target == null) {
+ Slog.w(TAG, "<" + name + "> without target in " + permFile
+ + " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ break;
+ }
+ String allowed = parser.getAttributeValue(null, "allowed");
+ if (allowed == null) {
+ Slog.w(TAG, "<" + name + "> without allowed in " + permFile
+ + " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ break;
+ }
+ target = target.intern();
+ allowed = allowed.intern();
+ ArraySet<String> associations = mAllowedAssociations.get(target);
+ if (associations == null) {
+ associations = new ArraySet<>();
+ mAllowedAssociations.put(target, associations);
+ }
+ Slog.i(TAG, "Adding association: " + target + " <- " + allowed);
+ associations.add(allowed);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
+ default: {
+ Slog.w(TAG, "Tag " + name + " is unknown in "
+ + permFile + " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ } break;
}
}
} catch (XmlPullParserException e) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8962e1d8c727..43f8d00bb5f1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -161,6 +161,7 @@ cc_library_shared {
"android/graphics/pdf/PdfUtils.cpp",
"android/graphics/text/LineBreaker.cpp",
"android/graphics/text/MeasuredText.cpp",
+ "android_media_AudioEffectDescriptor.cpp",
"android_media_AudioRecord.cpp",
"android_media_AudioSystem.cpp",
"android_media_AudioTrack.cpp",
@@ -263,6 +264,7 @@ cc_library_shared {
"libEGL",
"libGLESv1_CM",
"libGLESv2",
+ "libGLESv3",
"libvulkan",
"libziparchive",
"libETC1",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f9879ccc18c9..687b1055c3d6 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -103,6 +103,7 @@ extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
+extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
extern int register_android_media_AudioRecord(JNIEnv *env);
extern int register_android_media_AudioSystem(JNIEnv *env);
extern int register_android_media_AudioTrack(JNIEnv *env);
@@ -1456,6 +1457,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
+ REG_JNI(register_android_media_AudioEffectDescriptor),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioRecord),
REG_JNI(register_android_media_AudioTrack),
diff --git a/core/jni/android_media_AudioEffectDescriptor.cpp b/core/jni/android_media_AudioEffectDescriptor.cpp
new file mode 100644
index 000000000000..5175a05c4c3b
--- /dev/null
+++ b/core/jni/android_media_AudioEffectDescriptor.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core_jni_helpers.h"
+#include "android_media_AudioErrors.h"
+#include "media/AudioEffect.h"
+
+using namespace android;
+
+static jclass gAudioEffectDescriptorClass;
+static jmethodID gAudioEffectDescriptorCstor;
+
+namespace android {
+
+jclass audioEffectDescriptorClass() {
+ return gAudioEffectDescriptorClass;
+}
+
+jint convertAudioEffectDescriptorFromNative(JNIEnv* env, jobject* jDescriptor,
+ const effect_descriptor_t* nDescriptor)
+{
+ jstring jType;
+ jstring jUuid;
+ jstring jConnect;
+ jstring jName;
+ jstring jImplementor;
+ char str[EFFECT_STRING_LEN_MAX];
+
+ if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK)
+ == EFFECT_FLAG_TYPE_AUXILIARY) {
+ jConnect = env->NewStringUTF("Auxiliary");
+ } else if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK)
+ == EFFECT_FLAG_TYPE_INSERT) {
+ jConnect = env->NewStringUTF("Insert");
+ } else if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK)
+ == EFFECT_FLAG_TYPE_PRE_PROC) {
+ jConnect = env->NewStringUTF("Pre Processing");
+ } else {
+ return (jint) AUDIO_JAVA_BAD_VALUE;
+ }
+
+ AudioEffect::guidToString(&nDescriptor->type, str, EFFECT_STRING_LEN_MAX);
+ jType = env->NewStringUTF(str);
+
+ AudioEffect::guidToString(&nDescriptor->uuid, str, EFFECT_STRING_LEN_MAX);
+ jUuid = env->NewStringUTF(str);
+
+ jName = env->NewStringUTF(nDescriptor->name);
+ jImplementor = env->NewStringUTF(nDescriptor->implementor);
+
+ *jDescriptor = env->NewObject(gAudioEffectDescriptorClass,
+ gAudioEffectDescriptorCstor,
+ jType,
+ jUuid,
+ jConnect,
+ jName,
+ jImplementor);
+ env->DeleteLocalRef(jType);
+ env->DeleteLocalRef(jUuid);
+ env->DeleteLocalRef(jConnect);
+ env->DeleteLocalRef(jName);
+ env->DeleteLocalRef(jImplementor);
+
+ return (jint) AUDIO_JAVA_SUCCESS;
+}
+
+void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDescriptors,
+ const std::vector<effect_descriptor_t>& nDescriptors)
+{
+ jobjectArray temp = env->NewObjectArray(nDescriptors.size(),
+ audioEffectDescriptorClass(), NULL);
+ size_t actualSize = 0;
+ for (size_t i = 0; i < nDescriptors.size(); i++) {
+ jobject jdesc;
+ if (convertAudioEffectDescriptorFromNative(env,
+ &jdesc,
+ &nDescriptors[i])
+ != AUDIO_JAVA_SUCCESS) {
+ continue;
+ }
+
+ env->SetObjectArrayElement(temp, actualSize++, jdesc);
+ env->DeleteLocalRef(jdesc);
+ }
+
+ *jDescriptors = env->NewObjectArray(actualSize, audioEffectDescriptorClass(), NULL);
+ for (size_t i = 0; i < actualSize; i++) {
+ env->SetObjectArrayElement(*jDescriptors,
+ i,
+ env->GetObjectArrayElement(temp, i));
+ }
+ env->DeleteLocalRef(temp);
+}
+
+}; // namespace android
+
+int register_android_media_AudioEffectDescriptor(JNIEnv* env) {
+ jclass audioEffectDescriptorClass =
+ FindClassOrDie(env, "android/media/audiofx/AudioEffect$Descriptor");
+ gAudioEffectDescriptorClass =
+ MakeGlobalRefOrDie(env, audioEffectDescriptorClass);
+ gAudioEffectDescriptorCstor =
+ GetMethodIDOrDie(env,
+ audioEffectDescriptorClass,
+ "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+
+ env->DeleteLocalRef(audioEffectDescriptorClass);
+ return 0;
+}
diff --git a/core/jni/android_media_AudioEffectDescriptor.h b/core/jni/android_media_AudioEffectDescriptor.h
new file mode 100644
index 000000000000..d07188c8bdb1
--- /dev/null
+++ b/core/jni/android_media_AudioEffectDescriptor.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_AUDIOEFFECT_DESCRIPTOR_H
+#define ANDROID_MEDIA_AUDIOEFFECT_DESCRIPTOR_H
+
+#include <system/audio.h>
+#include <system/audio_effect.h>
+
+#include "jni.h"
+
+namespace android {
+
+// Conversion from C effect_descriptor_t to Java AudioEffect.Descriptor object
+
+extern jclass audioEffectDescriptorClass();
+
+extern jint convertAudioEffectDescriptorFromNative(JNIEnv *env, jobject *jDescriptor,
+ const effect_descriptor_t *nDescriptor);
+
+extern void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDescriptors,
+ const std::vector<effect_descriptor_t>& nDescriptors);
+} // namespace android
+
+#endif \ No newline at end of file
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 4f8bbc1396c8..3329e2047085 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -919,7 +919,7 @@ static jlong android_os_Binder_clearCallingWorkSource()
return IPCThreadState::self()->clearCallingWorkSource();
}
-static void android_os_Binder_restoreCallingWorkSource(long token)
+static void android_os_Binder_restoreCallingWorkSource(jlong token)
{
IPCThreadState::self()->restoreCallingWorkSource(token);
}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 377e65c33dd0..102a0b7b8957 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1137,7 +1137,7 @@ static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid
UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
char line[256];
- while (fgets(line, sizeof(line), file.get())) {
+ while (file != nullptr && fgets(line, sizeof(line), file.get())) {
jlong v;
if ( sscanf(line, "VmRSS: %" SCNd64 " kB", &v) == 1) {
rss[0] = v;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ea6e0178bd9c..c745c160e143 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -37,6 +37,7 @@
#include <stdio.h>
#include <system/graphics.h>
#include <ui/DisplayInfo.h>
+#include <ui/DisplayedFrameStats.h>
#include <ui/FrameStats.h>
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
@@ -97,6 +98,16 @@ static struct {
jmethodID builder;
} gGraphicBufferClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gDisplayedContentSampleClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gDisplayedContentSamplingAttributesClassInfo;
+
// ----------------------------------------------------------------------------
static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) {
@@ -398,6 +409,73 @@ static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) {
return javaObjectForIBinder(env, token);
}
+static jobject nativeGetDisplayedContentSamplingAttributes(JNIEnv* env, jclass clazz,
+ jobject tokenObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+
+ ui::PixelFormat format;
+ ui::Dataspace dataspace;
+ uint8_t componentMask;
+ status_t err = SurfaceComposerClient::getDisplayedContentSamplingAttributes(
+ token, &format, &dataspace, &componentMask);
+ if (err != OK) {
+ return nullptr;
+ }
+ return env->NewObject(gDisplayedContentSamplingAttributesClassInfo.clazz,
+ gDisplayedContentSamplingAttributesClassInfo.ctor,
+ format, dataspace, componentMask);
+}
+
+static jboolean nativeSetDisplayedContentSamplingEnabled(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jboolean enable, jint componentMask, jint maxFrames) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ return SurfaceComposerClient::setDisplayContentSamplingEnabled(
+ token, enable, componentMask, maxFrames);
+}
+
+static jobject nativeGetDisplayedContentSample(JNIEnv* env, jclass clazz, jobject tokenObj,
+ jlong maxFrames, jlong timestamp) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+
+ DisplayedFrameStats stats;
+ status_t err = SurfaceComposerClient::getDisplayedContentSample(
+ token, maxFrames, timestamp, &stats);
+ if (err != OK) {
+ return nullptr;
+ }
+
+ jlongArray histogramComponent0 = env->NewLongArray(stats.component_0_sample.size());
+ jlongArray histogramComponent1 = env->NewLongArray(stats.component_1_sample.size());
+ jlongArray histogramComponent2 = env->NewLongArray(stats.component_2_sample.size());
+ jlongArray histogramComponent3 = env->NewLongArray(stats.component_3_sample.size());
+ if ((histogramComponent0 == nullptr) ||
+ (histogramComponent1 == nullptr) ||
+ (histogramComponent2 == nullptr) ||
+ (histogramComponent3 == nullptr)) {
+ return JNI_FALSE;
+ }
+
+ env->SetLongArrayRegion(histogramComponent0, 0,
+ stats.component_0_sample.size(),
+ reinterpret_cast<jlong*>(stats.component_0_sample.data()));
+ env->SetLongArrayRegion(histogramComponent1, 0,
+ stats.component_1_sample.size(),
+ reinterpret_cast<jlong*>(stats.component_1_sample.data()));
+ env->SetLongArrayRegion(histogramComponent2, 0,
+ stats.component_2_sample.size(),
+ reinterpret_cast<jlong*>(stats.component_2_sample.data()));
+ env->SetLongArrayRegion(histogramComponent3, 0,
+ stats.component_3_sample.size(),
+ reinterpret_cast<jlong*>(stats.component_3_sample.data()));
+ return env->NewObject(gDisplayedContentSampleClassInfo.clazz,
+ gDisplayedContentSampleClassInfo.ctor,
+ stats.numFrames,
+ histogramComponent0,
+ histogramComponent1,
+ histogramComponent2,
+ histogramComponent3);
+}
+
static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj,
jboolean secure) {
ScopedUtfChars name(env, nameObj);
@@ -955,6 +1033,14 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeCaptureLayers },
{"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
(void*)nativeSetInputWindowInfo },
+ {"nativeGetDisplayedContentSamplingAttributes",
+ "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;",
+ (void*)nativeGetDisplayedContentSamplingAttributes },
+ {"nativeSetDisplayedContentSamplingEnabled", "(Landroid/os/IBinder;ZII)Z",
+ (void*)nativeSetDisplayedContentSamplingEnabled },
+ {"nativeGetDisplayedContentSample",
+ "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;",
+ (void*)nativeGetDisplayedContentSample },
};
int register_android_view_SurfaceControl(JNIEnv* env)
@@ -1009,6 +1095,18 @@ int register_android_view_SurfaceControl(JNIEnv* env)
gGraphicBufferClassInfo.builder = GetStaticMethodIDOrDie(env, graphicsBufferClazz,
"createFromExisting", "(IIIIJ)Landroid/graphics/GraphicBuffer;");
+ jclass displayedContentSampleClazz = FindClassOrDie(env,
+ "android/hardware/display/DisplayedContentSample");
+ gDisplayedContentSampleClassInfo.clazz = MakeGlobalRefOrDie(env, displayedContentSampleClazz);
+ gDisplayedContentSampleClassInfo.ctor = GetMethodIDOrDie(env,
+ displayedContentSampleClazz, "<init>", "(J[J[J[J[J)V");
+
+ jclass displayedContentSamplingAttributesClazz = FindClassOrDie(env,
+ "android/hardware/display/DisplayedContentSamplingAttributes");
+ gDisplayedContentSamplingAttributesClassInfo.clazz = MakeGlobalRefOrDie(env,
+ displayedContentSamplingAttributesClazz);
+ gDisplayedContentSamplingAttributesClassInfo.ctor = GetMethodIDOrDie(env,
+ displayedContentSamplingAttributesClazz, "<init>", "(III)V");
return err;
}
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
index b708735616c4..24bafca9c386 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
@@ -35,7 +35,6 @@
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
-using android::bpf::hasBpfSupport;
using android::bpf::parseBpfNetworkStatsDetail;
using android::bpf::stats_line;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 4aa88e7558cd..7032081ef6a0 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -99,7 +99,8 @@ enum MountExternalKind {
MOUNT_EXTERNAL_DEFAULT = 1,
MOUNT_EXTERNAL_READ = 2,
MOUNT_EXTERNAL_WRITE = 3,
- MOUNT_EXTERNAL_FULL = 4,
+ MOUNT_EXTERNAL_INSTALLER = 4,
+ MOUNT_EXTERNAL_FULL = 5,
};
// Must match values in com.android.internal.os.Zygote.
@@ -446,29 +447,35 @@ static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::st
return true;
}
-static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
- const std::string& mntTargetRoot, const std::string& packageName,
- const char* dirName, std::string* error_msg) {
- std::string mntSourceDir = StringPrintf("%s/Android/%s/%s",
- mntSourceRoot.c_str(), dirName, packageName.c_str());
- std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
- mntTargetRoot.c_str(), dirName, packageName.c_str());
- if (TEMP_FAILURE_RETRY(mount(mntSourceDir.c_str(), mntTargetDir.c_str(),
+static bool bindMount(const std::string& sourceDir, const std::string& targetDir,
+ std::string* error_msg) {
+ if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(),
nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
*error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
- mntSourceDir.c_str(), mntTargetDir.c_str(), strerror(errno));
+ sourceDir.c_str(), targetDir.c_str(), strerror(errno));
return false;
}
- if (TEMP_FAILURE_RETRY(mount(nullptr, mntTargetDir.c_str(),
+ if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(),
nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", mntTargetDir.c_str());
+ *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str());
return false;
}
return true;
}
+static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
+ const std::string& mntTargetRoot, const std::string& packageName,
+ const char* dirName, std::string* error_msg) {
+ std::string mntSourceDir = StringPrintf("%s/Android/%s/%s",
+ mntSourceRoot.c_str(), dirName, packageName.c_str());
+ std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
+ mntTargetRoot.c_str(), dirName, packageName.c_str());
+ return bindMount(mntSourceDir, mntTargetDir, error_msg);
+}
+
static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
- const std::vector<std::string>& volumeLabels, userid_t userId, std::string* error_msg) {
+ const std::vector<std::string>& volumeLabels, bool mountAllObbs,
+ userid_t userId, std::string* error_msg) {
for (auto& label : volumeLabels) {
std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str());
std::string mntTarget = StringPrintf("/storage/%s", label.c_str());
@@ -479,7 +486,14 @@ static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
for (auto& package : packageNames) {
mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg);
mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg);
- mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+ if (!mountAllObbs) {
+ mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+ }
+ }
+ if (mountAllObbs) {
+ StringAppendF(&mntSource, "/Android/obb");
+ StringAppendF(&mntTarget, "/Android/obb");
+ bindMount(mntSource, mntTarget, error_msg);
}
}
return true;
@@ -500,7 +514,7 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
storageSource = "/mnt/runtime/read";
} else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
storageSource = "/mnt/runtime/write";
- } else if (mount_mode != MOUNT_EXTERNAL_FULL && !force_mount_namespace) {
+ } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
// Sane default of no storage visible
return true;
}
@@ -568,12 +582,28 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
pkgSandboxDir.c_str(), strerror(errno));
return false;
}
+ if (access("/storage/obb_mount", F_OK) == 0) {
+ if (mount_mode != MOUNT_EXTERNAL_INSTALLER) {
+ remove("/storage/obb_mount");
+ }
+ } else {
+ if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+ int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount",
+ O_RDWR | O_CREAT, 0660));
+ if (fd == -1) {
+ *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
+ strerror(errno));
+ return false;
+ }
+ close(fd);
+ }
+ }
// If the sandbox was already created by vold, only then set up the bind mounts for
// pkg specific directories. Otherwise, leave as is and bind mounts will be taken
// care of by vold later.
if (sandboxAlreadyCreated) {
if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
- user_id, error_msg)) {
+ mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) {
return false;
}
}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index c2bc7bf91be9..7fe3be870994 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -417,7 +417,8 @@ message StateControllerProto {
optional int64 start_time_elapsed = 1;
optional int64 end_time_elapsed = 2;
- optional int32 job_count = 3;
+ // The number of background jobs that ran during this session.
+ optional int32 bg_job_count = 3;
}
message Timer {
@@ -428,8 +429,9 @@ message StateControllerProto {
optional bool is_active = 2;
// The time this timer last became active. Only valid if is_active is true.
optional int64 start_time_elapsed = 3;
- // How many are currently running. Valid only if the device is_active is true.
- optional int32 job_count = 4;
+ // How many background jobs are currently running. Valid only if the device is_active
+ // is true.
+ optional int32 bg_job_count = 4;
// All of the jobs that the Timer is currently tracking.
repeated JobStatusShortInfoProto running_jobs = 5;
}
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 3d60a86d86c9..528c1a4134f1 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -54,6 +54,9 @@ message IntervalStatsProto {
// Time attributes stored as an offset of the IntervalStats's beginTime.
optional int64 last_time_service_used_ms = 8;
optional int64 total_time_service_used_ms = 9;
+ // Time attributes stored as an offset of the IntervalStats's beginTime.
+ optional int64 last_time_visible_ms = 10;
+ optional int64 total_time_visible_ms = 11;
}
// Stores the relevant information an IntervalStats will have about a Configuration
@@ -82,6 +85,9 @@ message IntervalStatsProto {
optional string notification_channel = 12;
// notification_channel_index contains the index + 1 of the channel name in the string pool
optional int32 notification_channel_index = 13;
+ // If class field is an Activity, instance_id is a unique id of the
+ // Activity object.
+ optional int32 instance_id = 14;
}
// The following fields contain supplemental data used to build IntervalStats, such as a string
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ff73df6cb876..dca15bd49ca4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1120,6 +1120,18 @@
android:description="@string/permdesc_manageOwnCalls"
android:protectionLevel="normal" />
+ <!--Allows an app which implements the
+ {@link android.telecom.InCallService InCallService} API to be eligible to be enabled as a
+ calling companion app. This means that the Telecom framework will bind to the app's
+ InCallService implementation when there are calls active. The app can use the InCallService
+ API to view information about calls on the system and control these calls.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CALL_COMPANION_APP"
+ android:label="@string/permlab_callCompanionApp"
+ android:description="@string/permdesc_callCompanionApp"
+ android:protectionLevel="normal" />
+
<!-- Allows a calling app to continue a call which was started in another app. An example is a
video calling app that wants to continue a voice call on the user's mobile network.<p>
When the handover of a call from one app to another takes place, there are two devices
@@ -2093,10 +2105,9 @@
<!-- @hide Allows an application to cache content.
<p>Not for use by third-party applications.
- <p>Protection level: signature
-->
<permission android:name="android.permission.CACHE_CONTENT"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|documenter" />
<!-- @SystemApi @hide
Allows an application to aggressively allocate disk space.
@@ -4685,6 +4696,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.pm.DynamicCodeLoggingService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.PruneInstantAppsJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index ab4bd0534025..35263a3fa891 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1603,6 +1603,9 @@
<!-- Declares that this application should be invoked without non-SDK API enforcement -->
<attr name="usesNonSdkApi" />
+ <!-- If {@code true} the user is prompted to keep the app's data on uninstall -->
+ <attr name="hasFragileUserData" />
+
</declare-styleable>
<!-- The <code>permission</code> tag declares a security permission that can be
used to control access from other packages to specific components or
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 101f92b2097c..97a21a55f67f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -222,6 +222,11 @@
so that applications can still use their own mechanisms. -->
<bool name="config_enableAutoPowerModes">false</bool>
+ <!-- Whether (if true) this is a kind of device that can be moved around (eg. phone/laptop),
+ or (if false) something for which movement is either not measurable or should not count
+ toward power states (eg. tv/soundbar). -->
+ <bool name="config_autoPowerModeUseMotionSensor">true</bool>
+
<!-- The threshold angle for any motion detection in auto-power save modes.
In hundreths of a degree. -->
<integer name="config_autoPowerModeThresholdAngle">200</integer>
@@ -3592,4 +3597,9 @@
<!-- Component name for default assistant on this device -->
<string name="config_defaultAssistantComponentName">#+UNSET</string>
+ <!-- Class name for the InputEvent compatibility processor override.
+ Empty string means use the default compatibility processor
+ (android.view.InputEventCompatProcessor). -->
+ <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
+
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d480121fc998..eac8b4854994 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2930,6 +2930,7 @@
<public name="dataRetentionTime" />
<public name="selectionDividerHeight" />
<public name="foregroundServiceType" />
+ <public name="hasFragileUserData" />
</public-group>
<public-group type="drawable" first-id="0x010800b4">
@@ -2970,6 +2971,8 @@
<public-group type="dimen" first-id="0x01050007">
<!-- @hide @SystemApi -->
<public name="config_restrictedIconSize" />
+ <!-- @hide @SystemApi -->
+ <public name="config_mediaMetadataBitmapMaxSize" />
</public-group>
<!-- ===============================================================
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0a167ddc7085..cab01f9c027f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1215,6 +1215,15 @@
<string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in
order to improve the calling experience.</string>
+ <!-- Title of an application permission. When granted the app is allowed to be enabled as
+ a companion app. [CHAR LIMIT=NONE]-->
+ <string name="permlab_callCompanionApp">see and control calls through the system.</string>
+ <!-- Description of an application permission. When granted the app is allowed to be enabled as
+ a companion app. [CHAR LIMIT=NONE]-->
+ <string name="permdesc_callCompanionApp">Allows the app to see and control ongoing calls on the
+ device. This includes information such as call numbers for calls and the state of the
+ calls.</string>
+
<!-- Title of an application permission. When granted the user is giving access to a third
party app to continue a call which originated in another app. For example, the user
could be in a voice call over their carrier's mobile network, and a third party video
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d573c09a6538..161e41681486 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -265,6 +265,7 @@
<java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" />
<java-symbol type="bool" name="config_autoPowerModePreferWristTilt" />
<java-symbol type="bool" name="config_autoPowerModePrefetchLocation" />
+ <java-symbol type="bool" name="config_autoPowerModeUseMotionSensor" />
<java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
<java-symbol type="bool" name="config_enable_puk_unlock_screen" />
<java-symbol type="bool" name="config_disableLockscreenByDefault" />
@@ -301,6 +302,7 @@
<java-symbol type="bool" name="config_enableWallpaperService" />
<java-symbol type="bool" name="config_checkWallpaperAtBoot" />
<java-symbol type="string" name="config_wallpaperManagerServiceName" />
+ <java-symbol type="string" name="config_inputEventCompatProcessorOverrideClassName" />
<java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
<java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
<java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index d289f1f5defc..9b5b725a3bed 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -16,9 +16,16 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.app.admin.PasswordMetrics.PasswordComplexityBucket;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -164,4 +171,126 @@ public class PasswordMetricsTest {
}
+
+ @Test
+ public void testConstructQuality() {
+ PasswordMetrics expected = new PasswordMetrics();
+ expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+
+ PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testDetermineComplexity_none() {
+ assertEquals(PASSWORD_COMPLEXITY_NONE,
+ PasswordMetrics.computeForPassword("").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_lowSomething() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW,
+ new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_lowNumeric() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW,
+ PasswordMetrics.computeForPassword("1234").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_lowNumericComplex() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW,
+ PasswordMetrics.computeForPassword("124").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_lowAlphabetic() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW,
+ PasswordMetrics.computeForPassword("a!").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_lowAlphanumeric() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW,
+ PasswordMetrics.computeForPassword("a!1").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_mediumNumericComplex() {
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+ PasswordMetrics.computeForPassword("1238").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_mediumAlphabetic() {
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+ PasswordMetrics.computeForPassword("ab!c").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_mediumAlphanumeric() {
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+ PasswordMetrics.computeForPassword("ab!1").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_highNumericComplex() {
+ assertEquals(PASSWORD_COMPLEXITY_HIGH,
+ PasswordMetrics.computeForPassword("12389647!").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_highAlphabetic() {
+ assertEquals(PASSWORD_COMPLEXITY_HIGH,
+ PasswordMetrics.computeForPassword("alphabetic!").determineComplexity());
+ }
+
+ @Test
+ public void testDetermineComplexity_highAlphanumeric() {
+ assertEquals(PASSWORD_COMPLEXITY_HIGH,
+ PasswordMetrics.computeForPassword("alphanumeric123!").determineComplexity());
+ }
+
+ @Test
+ public void testComplexityLevelToBucket_none() {
+ PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+ PASSWORD_COMPLEXITY_NONE).getMetrics();
+
+ for (PasswordMetrics metrics : bucket) {
+ assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity());
+ }
+ }
+
+ @Test
+ public void testComplexityLevelToBucket_low() {
+ PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+ PASSWORD_COMPLEXITY_LOW).getMetrics();
+
+ for (PasswordMetrics metrics : bucket) {
+ assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity());
+ }
+ }
+
+ @Test
+ public void testComplexityLevelToBucket_medium() {
+ PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+ PASSWORD_COMPLEXITY_MEDIUM).getMetrics();
+
+ for (PasswordMetrics metrics : bucket) {
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity());
+ }
+ }
+
+ @Test
+ public void testComplexityLevelToBucket_high() {
+ PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+ PASSWORD_COMPLEXITY_HIGH).getMetrics();
+
+ for (PasswordMetrics metrics : bucket) {
+ assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity());
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
index 1f047f9e6d10..28aaf1e05644 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
@@ -16,18 +16,22 @@
package android.app.usage;
-import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
import static android.app.usage.UsageEvents.Event.END_OF_DAY;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND;
import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import android.app.usage.UsageEvents.Event;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -120,10 +124,10 @@ public class UsageStatsTest {
left.mBeginTimeStamp = 100000;
left.mTotalTimeInForeground = 10;
- left.mLastForegroundActivityEventMap.put("com.test.activity1", MOVE_TO_FOREGROUND);
- left.mLastForegroundActivityEventMap.put("com.test.activity2", MOVE_TO_FOREGROUND);
- left.mLastForegroundServiceEventMap.put("com.test.service1", FOREGROUND_SERVICE_START);
- left.mLastForegroundServiceEventMap.put("com.test.service2", FOREGROUND_SERVICE_START);
+ left.mActivities.put(1, Event.ACTIVITY_RESUMED);
+ left.mActivities.put(2, Event.ACTIVITY_RESUMED);
+ left.mForegroundServices.put("com.test.service1", FOREGROUND_SERVICE_START);
+ left.mForegroundServices.put("com.test.service2", FOREGROUND_SERVICE_START);
Parcel p = Parcel.obtain();
left.writeToParcel(p, 0);
@@ -133,131 +137,182 @@ public class UsageStatsTest {
}
@Test
- public void testForegroundActivity() {
+ public void testActivity() {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND);
+ left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1);
assertEquals(left.mLastTimeUsed, 200000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(MOVE_TO_FOREGROUND));
+ assertEquals(left.mLastTimeVisible, 200000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
assertEquals(left.mLaunchCount, 1);
+ assertEquals(left.mTotalTimeInForeground, 0);
+ assertEquals(left.mTotalTimeVisible, 0);
- left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1);
assertEquals(left.mLastTimeUsed, 350000);
- assertFalse(left.mLastForegroundActivityEventMap.containsKey("com.test.activity1"));
+ assertEquals(left.mLastTimeVisible, 350000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 350000 - 200000);
+ assertEquals(left.mTotalTimeVisible, 350000 - 200000);
+
+ left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1);
+ assertEquals(left.mLastTimeUsed, 350000);
+ assertEquals(left.mLastTimeVisible, 400000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+ assertEquals(left.mTotalTimeInForeground, 350000 - 200000);
+ assertEquals(left.mTotalTimeVisible, 400000 - 200000);
+
+ left.update("com.test.activity1", 500000, ACTIVITY_DESTROYED, 1);
+ assertEquals(left.mLastTimeUsed, 350000);
+ assertEquals(left.mLastTimeVisible, 400000);
+ assertTrue(left.mActivities.indexOfKey(1) < 0);
+ assertEquals(left.mTotalTimeInForeground, 350000 - 200000);
+ assertEquals(left.mTotalTimeVisible, 400000 - 200000);
}
@Test
- public void testEvent_CONTINUE_PREVIOUS_DAY() {
+ public void testEvent_END_OF_DAY() {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
+ left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
assertEquals(left.mLastTimeUsed, 100000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLaunchCount, 0);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+ assertEquals(left.mLaunchCount, 1);
- left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+ left.update(null, 350000, END_OF_DAY, 0);
assertEquals(left.mLastTimeUsed, 350000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+ assertEquals(left.mLastTimeVisible, 350000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
assertEquals(left.mTotalTimeInForeground, 350000 - 100000);
+ assertEquals(left.mTotalTimeVisible, 350000 - 100000);
}
@Test
- public void testEvent_END_OF_DAY() {
+ public void testEvent_ACTIVITY_PAUSED() {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
- assertEquals(left.mLastTimeUsed, 100000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLaunchCount, 0);
+ left.update("com.test.activity1", 100000, ACTIVITY_PAUSED, 1);
+ assertEquals(left.mLastTimeUsed, 0);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
- left.update(null, 350000, END_OF_DAY);
- assertEquals(left.mLastTimeUsed, 350000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(END_OF_DAY));
- assertEquals(left.mTotalTimeInForeground, 350000 - 100000);
+ left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1);
+ assertEquals(left.mLastTimeUsed, 200000);
+ assertEquals(left.mLastTimeVisible, 200000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+ assertEquals(left.mTotalTimeInForeground, 0);
+ assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+
+ left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1);
+ assertEquals(left.mLastTimeUsed, 300000);
+ assertEquals(left.mLastTimeVisible, 300000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
+ assertEquals(left.mTotalTimeInForeground, 300000 - 200000);
+ assertEquals(left.mTotalTimeVisible, 300000 - 100000);
+
+ left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1);
+ assertEquals(left.mLastTimeUsed, 300000);
+ assertEquals(left.mLastTimeVisible, 400000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+ assertEquals(left.mTotalTimeInForeground, 300000 - 200000);
+ assertEquals(left.mTotalTimeVisible, 400000 - 100000);
}
@Test
- public void testForegroundActivityEventSequence() {
+ public void testEvent_CHANGE_TO_INVISIBLE() {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
+ left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1);
assertEquals(left.mLastTimeUsed, 100000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLaunchCount, 0);
-
- left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
- assertEquals(left.mLastTimeUsed, 350000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
- assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED);
- left.update("com.test.activity1", 450000, MOVE_TO_FOREGROUND);
- assertEquals(left.mLastTimeUsed, 450000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(MOVE_TO_FOREGROUND));
- assertEquals(left.mTotalTimeInForeground, 250000);
-
- left.update("com.test.activity1", 500000, MOVE_TO_BACKGROUND);
- assertEquals(left.mLastTimeUsed, 500000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
- assertEquals(left.mTotalTimeInForeground, 250000 + 50000 /*500000 - 450000*/);
+ left.update("com.test.activity1", 200000, ACTIVITY_STOPPED, 1);
+ assertEquals(left.mLastTimeUsed, 200000);
+ assertEquals(left.mLastTimeVisible, 200000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+ assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+ assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+
+ left.update("com.test.activity1", 300000, ACTIVITY_RESUMED, 1);
+ assertEquals(left.mLastTimeUsed, 300000);
+ assertEquals(left.mLastTimeVisible, 300000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED);
+ assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+ assertEquals(left.mTotalTimeVisible, 200000 - 100000);
}
@Test
- public void testForegroundActivityEventOutOfSequence() {
+ public void testEvent_ACTIVITY_DESTROYED() {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
+ left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1);
assertEquals(left.mLastTimeUsed, 100000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLaunchCount, 0);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED);
- left.update("com.test.activity1", 150000, MOVE_TO_FOREGROUND);
- assertEquals(left.mLastTimeUsed, 150000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(MOVE_TO_FOREGROUND));
+ left.update("com.test.activity1", 200000, ACTIVITY_DESTROYED, 1);
+ assertEquals(left.mLastTimeUsed, 200000);
+ assertEquals(left.mLastTimeVisible, 200000);
+ assertTrue(left.mActivities.indexOfKey(1) < 0);
+ assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+ assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+ }
+
+ @Test
+ public void testActivityEventOutOfOrder() {
+ left.mPackageName = "com.test";
+ left.mBeginTimeStamp = 100000;
+
+ left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+ assertEquals(left.mLastTimeUsed, 100000);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
assertEquals(left.mLaunchCount, 1);
- assertEquals(left.mTotalTimeInForeground, 50000 /*150000 - 100000*/);
+ assertEquals(left.mTotalTimeInForeground, 0);
+ assertEquals(left.mTotalTimeVisible, 0);
- left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND);
+ left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1);
assertEquals(left.mLastTimeUsed, 200000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(MOVE_TO_FOREGROUND));
+ assertEquals(left.mLastTimeVisible, 200000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
assertEquals(left.mLaunchCount, 2);
assertEquals(left.mTotalTimeInForeground, 100000);
+ assertEquals(left.mTotalTimeVisible, 100000 /*200000 - 100000*/);
- left.update("com.test.activity1", 250000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity1", 250000, ACTIVITY_PAUSED, 1);
assertEquals(left.mLastTimeUsed, 250000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+ assertEquals(left.mLastTimeVisible, 250000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 150000);
+ assertEquals(left.mTotalTimeVisible, 150000 /*250000 - 100000*/);
- left.update("com.test.activity1", 300000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1);
assertEquals(left.mLastTimeUsed, 250000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+ assertEquals(left.mLastTimeVisible, 300000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 150000);
+ assertEquals(left.mTotalTimeVisible, 200000 /*300000 - 100000*/);
- left.update("com.test.activity1", 350000, MOVE_TO_FOREGROUND);
+ left.update("com.test.activity1", 350000, Event.ACTIVITY_RESUMED, 1);
assertEquals(left.mLastTimeUsed, 350000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(MOVE_TO_FOREGROUND));
+ assertEquals(left.mLastTimeVisible, 350000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
assertEquals(left.mTotalTimeInForeground, 150000);
+ assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/);
- left.update("com.test.activity1", 400000, END_OF_DAY);
+ left.update("com.test.activity1", 400000, END_OF_DAY, 1);
assertEquals(left.mLastTimeUsed, 400000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(END_OF_DAY));
+ assertEquals(left.mLastTimeVisible, 400000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
assertEquals(left.mTotalTimeInForeground, 200000);
+ assertEquals(left.mTotalTimeVisible, 300000 /*400000 - 100000*/);
}
@Test
@@ -265,28 +320,41 @@ public class UsageStatsTest {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
- left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY);
+ left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+ left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2);
assertEquals(left.mLastTimeUsed, 100000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLaunchCount, 0);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+ assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED);
+ assertEquals(left.mLaunchCount, 2);
- left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1);
assertEquals(left.mLastTimeUsed, 350000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+ assertEquals(left.mLastTimeVisible, 350000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/);
+ assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/);
- left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2);
assertEquals(left.mLastTimeUsed, 450000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null);
+ assertEquals(left.mLastTimeVisible, 450000);
+ assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/);
+ assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/);
+
+ left.update("com.test.activity1", 550000, ACTIVITY_STOPPED, 1);
+ assertEquals(left.mLastTimeUsed, 450000);
+ assertEquals(left.mLastTimeVisible, 550000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+ assertEquals(left.mTotalTimeInForeground, 350000);
+ assertEquals(left.mTotalTimeVisible, 350000 + 100000 /*550000 - 450000*/);
- left.update(null, 500000, END_OF_DAY);
+ left.update("com.test.activity2", 650000, ACTIVITY_STOPPED, 2);
assertEquals(left.mLastTimeUsed, 450000);
+ assertEquals(left.mLastTimeVisible, 650000);
+ assertEquals(left.mActivities.get(2), ACTIVITY_STOPPED);
assertEquals(left.mTotalTimeInForeground, 350000);
+ assertEquals(left.mTotalTimeVisible, 450000 + 100000 /*650000 - 550000*/);
}
@Test
@@ -294,15 +362,14 @@ public class UsageStatsTest {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START);
+ left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 200000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(FOREGROUND_SERVICE_START));
- assertEquals(left.mLaunchCount, 0);
- left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+ assertEquals(left.mForegroundServices.get("com.test.service1"), null);
assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 200000);
}
@@ -311,15 +378,15 @@ public class UsageStatsTest {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.service1", 100000, CONTINUING_FOREGROUND_SERVICE);
+ left.update("com.test.service1", 100000,
+ CONTINUING_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
- assertEquals(left.mLaunchCount, 0);
- left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+ assertEquals(left.mForegroundServices.get("com.test.service1"), null);
assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000);
}
@@ -329,16 +396,15 @@ public class UsageStatsTest {
left.mBeginTimeStamp = 100000;
left.update("com.test.service1", 100000,
- CONTINUING_FOREGROUND_SERVICE);
+ CONTINUING_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
- assertEquals(left.mLaunchCount, 0);
- left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE);
+ left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
- new Integer(ROLLOVER_FOREGROUND_SERVICE));
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
+ new Integer(CONTINUING_FOREGROUND_SERVICE));
assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000);
}
@@ -348,27 +414,28 @@ public class UsageStatsTest {
left.mBeginTimeStamp = 100000;
left.update("com.test.service1", 100000,
- CONTINUING_FOREGROUND_SERVICE);
+ CONTINUING_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
assertEquals(left.mLaunchCount, 0);
- left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+ assertEquals(left.mForegroundServices.get("com.test.service1"), null);
assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/);
- left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START);
+ left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 450000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(FOREGROUND_SERVICE_START));
assertEquals(left.mTotalTimeForegroundServiceUsed, 250000);
- left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 500000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
- assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 50000 /*500000 - 450000*/);
+ assertEquals(left.mForegroundServices.get("com.test.service1"), null);
+ assertEquals(left.mTotalTimeForegroundServiceUsed,
+ 250000 + 50000 /*500000 - 450000*/);
}
@Test
@@ -377,27 +444,27 @@ public class UsageStatsTest {
left.mBeginTimeStamp = 100000;
left.update("com.test.service1", 100000,
- CONTINUING_FOREGROUND_SERVICE);
+ CONTINUING_FOREGROUND_SERVICE, 0);
left.update("com.test.service2", 100000,
- CONTINUING_FOREGROUND_SERVICE);
+ CONTINUING_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"),
+ assertEquals(left.mForegroundServices.get("com.test.service2"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
- assertEquals(left.mLaunchCount, 0);
- left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+ assertEquals(left.mForegroundServices.get("com.test.service1"), null);
assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/);
- left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 450000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null);
- assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 100000 /*450000 - 350000*/);
+ assertEquals(left.mForegroundServices.get("com.test.service2"), null);
+ assertEquals(left.mTotalTimeForegroundServiceUsed,
+ 250000 + 100000 /*450000 - 350000*/);
- left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE);
+ left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 450000);
assertEquals(left.mTotalTimeForegroundServiceUsed, 350000);
}
@@ -407,76 +474,117 @@ public class UsageStatsTest {
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
- left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
- left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY);
+ left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+ left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2);
left.update("com.test.service1", 100000,
- CONTINUING_FOREGROUND_SERVICE);
+ CONTINUING_FOREGROUND_SERVICE, 0);
left.update("com.test.service2", 100000,
- CONTINUING_FOREGROUND_SERVICE);
+ CONTINUING_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeUsed, 100000);
assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"),
- new Integer(CONTINUE_PREVIOUS_DAY));
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+ assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED);
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"),
+ assertEquals(left.mForegroundServices.get("com.test.service2"),
new Integer(CONTINUING_FOREGROUND_SERVICE));
- assertEquals(left.mLaunchCount, 0);
+ assertEquals(left.mLaunchCount, 2);
- left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1);
assertEquals(left.mLastTimeUsed, 350000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+ assertEquals(left.mLastTimeVisible, 350000);
+ assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/);
+ assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/);
- left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 400000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+ assertEquals(left.mForegroundServices.get("com.test.service1"), null);
assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 /*400000 - 100000*/);
- left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND);
+ left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2);
assertEquals(left.mLastTimeUsed, 450000);
- assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null);
+ assertEquals(left.mLastTimeVisible, 450000);
+ assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED);
assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/);
+ assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/);
- left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP);
+ left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 500000);
- assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null);
- assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 + 100000 /*500000 - 400000*/);
+ assertEquals(left.mForegroundServices.get("com.test.service2"), null);
+ assertEquals(left.mTotalTimeForegroundServiceUsed,
+ 300000 + 100000 /*500000 - 400000*/);
- left.update(null, 550000, END_OF_DAY);
+ left.update(null, 550000, END_OF_DAY, 0);
assertEquals(left.mLastTimeUsed, 450000);
+ assertEquals(left.mLastTimeVisible, 550000);
assertEquals(left.mTotalTimeInForeground, 350000);
- left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE);
+ assertEquals(left.mTotalTimeVisible, 450000);
+
+ left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE, 0);
assertEquals(left.mLastTimeForegroundServiceUsed, 500000);
assertEquals(left.mTotalTimeForegroundServiceUsed, 400000);
}
+ @Test
+ public void testEvent_FLUSH_TO_DISK() {
+ testClosingEvent(FLUSH_TO_DISK);
+ }
+
+ private void testClosingEvent(int eventType) {
+ // When these three closing events are received, all open activities/services need to be
+ // closed and usage stats are updated.
+ if (eventType != FLUSH_TO_DISK) {
+ fail("Closing eventType must be one of FLUSH_TO_DISK");
+ }
+
+ left.mPackageName = "com.test";
+ left.mBeginTimeStamp = 100000;
+
+ left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+ assertEquals(left.mLastTimeUsed, 100000);
+ assertEquals(left.mLastTimeVisible, 100000);
+ assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+
+ left.update("com.test.service1", 150000, FOREGROUND_SERVICE_START, 0);
+ assertEquals(left.mLastTimeForegroundServiceUsed, 150000);
+ assertEquals(left.mForegroundServices.get("com.test.service1"),
+ new Integer(FOREGROUND_SERVICE_START));
+
+ left.update(null, 200000, eventType, 0);
+ assertEquals(left.mLastTimeUsed, 200000);
+ assertEquals(left.mLastTimeVisible, 200000);
+ assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+ assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+ assertEquals(left.mLastTimeForegroundServiceUsed, 200000);
+ assertEquals(left.mTotalTimeForegroundServiceUsed, 200000 - 150000);
+ }
+
void compareUsageStats(UsageStats us1, UsageStats us2) {
assertEquals(us1.mPackageName, us2.mPackageName);
assertEquals(us1.mBeginTimeStamp, us2.mBeginTimeStamp);
assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed);
+ assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible);
assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed);
assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground);
assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed);
assertEquals(us1.mAppLaunchCount, us2.mAppLaunchCount);
- assertEquals(us1.mLastForegroundActivityEventMap.size(),
- us2.mLastForegroundActivityEventMap.size());
- for (int i = 0; i < us1.mLastForegroundActivityEventMap.size(); i++) {
- assertEquals(us1.mLastForegroundActivityEventMap.keyAt(i),
- us2.mLastForegroundActivityEventMap.keyAt(i));
- assertEquals(us1.mLastForegroundActivityEventMap.valueAt(i),
- us2.mLastForegroundActivityEventMap.valueAt(i));
+ assertEquals(us1.mActivities.size(),
+ us2.mActivities.size());
+ for (int i = 0; i < us1.mActivities.size(); i++) {
+ assertEquals(us1.mActivities.keyAt(i),
+ us2.mActivities.keyAt(i));
+ assertEquals(us1.mActivities.valueAt(i),
+ us2.mActivities.valueAt(i));
}
- assertEquals(us1.mLastForegroundServiceEventMap.size(),
- us2.mLastForegroundServiceEventMap.size());
- for (int i = 0; i < us1.mLastForegroundServiceEventMap.size(); i++) {
- assertEquals(us1.mLastForegroundServiceEventMap.keyAt(i),
- us2.mLastForegroundServiceEventMap.keyAt(i));
- assertEquals(us1.mLastForegroundServiceEventMap.valueAt(i),
- us2.mLastForegroundServiceEventMap.valueAt(i));
+ assertEquals(us1.mForegroundServices.size(),
+ us2.mForegroundServices.size());
+ for (int i = 0; i < us1.mForegroundServices.size(); i++) {
+ assertEquals(us1.mForegroundServices.keyAt(i),
+ us2.mForegroundServices.keyAt(i));
+ assertEquals(us1.mForegroundServices.valueAt(i),
+ us2.mForegroundServices.valueAt(i));
}
assertEquals(us1.mChooserCounts, us2.mChooserCounts);
}
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
index d1dbd3ccba33..5664df6e9744 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
@@ -37,6 +38,7 @@ import org.junit.runner.RunWith;
* Test whether Binder calls work source is propagated correctly.
*/
@LargeTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class BinderWorkSourceTest {
private static Context sContext;
@@ -125,8 +127,10 @@ public class BinderWorkSourceTest {
Binder.setCallingWorkSourceUid(UID);
long token = Binder.clearCallingWorkSource();
Binder.restoreCallingWorkSource(token);
+ assertEquals(UID, Binder.getCallingWorkSourceUid());
assertEquals(UID, mService.getIncomingWorkSourceUid());
+ // Still the same after the binder transaction.
assertEquals(UID, Binder.getCallingWorkSourceUid());
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index f0faaf6153b1..4a6c093e3bd1 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
@@ -16,6 +16,9 @@
package android.view.textclassifier;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.Person;
@@ -27,16 +30,26 @@ import com.google.android.textclassifier.ActionsSuggestionsModel;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Locale;
+import java.util.function.Function;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ActionsSuggestionsHelperTest {
+ private static final String LOCALE_TAG = Locale.US.toLanguageTag();
+ private static final Function<CharSequence, String> LANGUAGE_DETECTOR =
+ charSequence -> LOCALE_TAG;
+
@Test
public void testToNativeMessages_emptyInput() {
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
- ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList());
+ ActionsSuggestionsHelper.toNativeMessages(
+ Collections.emptyList(), LANGUAGE_DETECTOR);
assertThat(conversationMessages).isEmpty();
}
@@ -44,114 +57,89 @@ public class ActionsSuggestionsHelperTest {
@Test
public void testToNativeMessages_noTextMessages() {
ConversationActions.Message messageWithoutText =
- new ConversationActions.Message.Builder().build();
+ new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build();
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
ActionsSuggestionsHelper.toNativeMessages(
- Collections.singletonList(messageWithoutText));
+ Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR);
assertThat(conversationMessages).isEmpty();
}
@Test
- public void testToNativeMessages_missingPersonInFirstMessage() {
- ConversationActions.Message firstMessage =
- new ConversationActions.Message.Builder()
- .setText("first")
- .build();
- ConversationActions.Message secondMessage =
- new ConversationActions.Message.Builder()
- .setText("second")
- .setAuthor(new Person.Builder().build())
- .build();
- ConversationActions.Message thirdMessage =
- new ConversationActions.Message.Builder()
- .setText("third")
- .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL)
- .build();
-
- ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
- ActionsSuggestionsHelper.toNativeMessages(
- Arrays.asList(firstMessage, secondMessage, thirdMessage));
-
- assertThat(conversationMessages).hasLength(2);
- assertNativeMessage(conversationMessages[0], secondMessage.getText(), 1);
- assertNativeMessage(conversationMessages[1], thirdMessage.getText(), 0);
- }
+ public void testToNativeMessages_userIdEncoding() {
+ Person userA = new Person.Builder().setName("userA").build();
+ Person userB = new Person.Builder().setName("userB").build();
- @Test
- public void testToNativeMessages_missingPersonInMiddleOfConversation() {
ConversationActions.Message firstMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(userB)
.setText("first")
- .setAuthor(new Person.Builder().setName("first").build())
.build();
ConversationActions.Message secondMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(userA)
.setText("second")
.build();
ConversationActions.Message thirdMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(PERSON_USER_LOCAL)
.setText("third")
- .setAuthor(new Person.Builder().setName("third").build())
.build();
ConversationActions.Message fourthMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(userA)
.setText("fourth")
- .setAuthor(new Person.Builder().setName("fourth").build())
.build();
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
ActionsSuggestionsHelper.toNativeMessages(
- Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage));
+ Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage),
+ LANGUAGE_DETECTOR);
- assertThat(conversationMessages).hasLength(2);
- assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2);
- assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1);
+ assertThat(conversationMessages).hasLength(4);
+ assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0);
+ assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0);
+ assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0);
+ assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0);
}
@Test
- public void testToNativeMessages_userIdEncoding() {
- Person userA = new Person.Builder().setName("userA").build();
- Person userB = new Person.Builder().setName("userB").build();
-
+ public void testToNativeMessages_referenceTime() {
ConversationActions.Message firstMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
.setText("first")
- .setAuthor(userB)
+ .setReferenceTime(createZonedDateTimeFromMsUtc(1000))
.build();
ConversationActions.Message secondMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
.setText("second")
- .setAuthor(userA)
.build();
ConversationActions.Message thirdMessage =
- new ConversationActions.Message.Builder()
+ new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
.setText("third")
- .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL)
- .build();
- ConversationActions.Message fourthMessage =
- new ConversationActions.Message.Builder()
- .setText("fourth")
- .setAuthor(userA)
+ .setReferenceTime(createZonedDateTimeFromMsUtc(2000))
.build();
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
ActionsSuggestionsHelper.toNativeMessages(
- Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage));
+ Arrays.asList(firstMessage, secondMessage, thirdMessage),
+ LANGUAGE_DETECTOR);
- assertThat(conversationMessages).hasLength(4);
- assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2);
- assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1);
- assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0);
- assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1);
+ assertThat(conversationMessages).hasLength(3);
+ assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000);
+ assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0);
+ assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000);
+ }
+
+ private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) {
+ return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC"));
}
private static void assertNativeMessage(
ActionsSuggestionsModel.ConversationMessage nativeMessage,
CharSequence text,
- int userId) {
+ int userId,
+ long referenceTimeInMsUtc) {
assertThat(nativeMessage.getText()).isEqualTo(text.toString());
assertThat(nativeMessage.getUserId()).isEqualTo(userId);
+ assertThat(nativeMessage.getLocales()).isEqualTo(LOCALE_TAG);
+ assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc);
}
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
index bae2be352b4c..aaadefb90ece 100644
--- a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
@@ -56,5 +56,7 @@ public class IntentFactoryTest {
Intent intent = labeledIntent.getIntent();
assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE);
assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT);
+ assertThat(
+ intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
}
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index aec4571252e7..9b5c0347bdb6 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -373,7 +373,10 @@ public class TextClassifierTest {
public void testSuggestConversationActions_textReplyOnly_maxThree() {
if (isTextClassifierDisabled()) return;
ConversationActions.Message message =
- new ConversationActions.Message.Builder().setText("Where are you?").build();
+ new ConversationActions.Message.Builder(
+ ConversationActions.Message.PERSON_USER_REMOTE)
+ .setText("Hello")
+ .build();
ConversationActions.TypeConfig typeConfig =
new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false)
.setIncludedTypes(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0a2f057ccb69..dcf95fdbb438 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -142,6 +142,7 @@ applications that come with the platform
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.CLEAR_APP_USER_DATA"/>
+ <permission name="android.permission.PACKAGE_USAGE_STATS"/>
</privapp-permissions>
<privapp-permissions package="com.android.permissioncontroller">
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 23ec5ab0d1f3..f1903a5a54a7 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,2 +1,3 @@
set noparent
toddke@google.com
+rtmitchell@google.com \ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 91261aa3e4f9..cf2d8fb3251c 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1622,7 +1622,7 @@ struct ResTable_overlayable_policy_header
{
struct ResChunk_header header;
- enum PolicyFlags {
+ enum PolicyFlags : uint32_t {
// Any overlay can overlay these resources.
POLICY_PUBLIC = 0x00000001,
diff --git a/location/java/android/location/SettingInjectorService.java b/location/java/android/location/SettingInjectorService.java
index fcd2cdec904f..c20177058b68 100644
--- a/location/java/android/location/SettingInjectorService.java
+++ b/location/java/android/location/SettingInjectorService.java
@@ -17,6 +17,7 @@
package android.location;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
@@ -26,7 +27,7 @@ import android.os.RemoteException;
import android.util.Log;
/**
- * Dynamically specifies the enabled status of a preference injected into
+ * Dynamically specifies the summary (subtitle) and enabled status of a preference injected into
* the list of app settings displayed by the system settings app
* <p/>
* For use only by apps that are included in the system image, for preferences that affect multiple
@@ -71,12 +72,13 @@ import android.util.Log;
* </ul>
*
* To ensure a good user experience, your {@link android.app.Application#onCreate()},
- * and {@link #onGetEnabled()} methods must all be fast. If either is slow,
- * it can delay the display of settings values for other apps as well. Note further that these
- * methods are called on your app's UI thread.
+ * {@link #onGetSummary()}, and {@link #onGetEnabled()} methods must all be fast. If any are slow,
+ * it can delay the display of settings values for other apps as well. Note further that all are
+ * called on your app's UI thread.
* <p/>
* For compactness, only one copy of a given setting should be injected. If each account has a
- * distinct value for the setting, then only {@code settingsActivity} should display the value for
+ * distinct value for the setting, then the {@link #onGetSummary()} value should represent a summary
+ * of the state across all of the accounts and {@code settingsActivity} should display the value for
* each account.
*/
public abstract class SettingInjectorService extends Service {
@@ -108,6 +110,14 @@ public abstract class SettingInjectorService extends Service {
"android.location.InjectedSettingChanged";
/**
+ * Name of the bundle key for the string specifying the summary for the setting (e.g., "ON" or
+ * "OFF").
+ *
+ * @hide
+ */
+ public static final String SUMMARY_KEY = "summary";
+
+ /**
* Name of the bundle key for the string specifying whether the setting is currently enabled.
*
* @hide
@@ -150,36 +160,41 @@ public abstract class SettingInjectorService extends Service {
}
private void onHandleIntent(Intent intent) {
-
- boolean enabled;
+ String summary = null;
+ boolean enabled = false;
try {
+ summary = onGetSummary();
enabled = onGetEnabled();
- } catch (RuntimeException e) {
- // Exception. Send status anyway, so that settings injector can immediately start
- // loading the status of the next setting.
- sendStatus(intent, true);
- throw e;
+ } finally {
+ // If exception happens, send status anyway, so that settings injector can immediately
+ // start loading the status of the next setting. But leave the exception uncaught to
+ // crash the injector service itself.
+ sendStatus(intent, summary, enabled);
}
-
- sendStatus(intent, enabled);
}
/**
* Send the enabled values back to the caller via the messenger encoded in the
* intent.
*/
- private void sendStatus(Intent intent, boolean enabled) {
+ private void sendStatus(Intent intent, String summary, boolean enabled) {
+ Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
+ // Bail out to avoid crashing GmsCore with incoming malicious Intent.
+ if (messenger == null) {
+ return;
+ }
+
Message message = Message.obtain();
Bundle bundle = new Bundle();
+ bundle.putString(SUMMARY_KEY, summary);
bundle.putBoolean(ENABLED_KEY, enabled);
message.setData(bundle);
if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, mName + ": received " + intent
+ Log.d(TAG, mName + ": received " + intent + ", summary=" + summary
+ ", enabled=" + enabled + ", sending message: " + message);
}
- Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
try {
messenger.send(message);
} catch (RemoteException e) {
@@ -188,14 +203,12 @@ public abstract class SettingInjectorService extends Service {
}
/**
- * This method is no longer called, because status values are no longer shown for any injected
- * setting.
- *
- * @return ignored
+ * Returns the {@link android.preference.Preference#getSummary()} value (allowed to be null or
+ * empty). Should not perform unpredictably-long operations such as network access--see the
+ * running-time comments in the class-level javadoc.
*
- * @deprecated not called any more
+ * @return the {@link android.preference.Preference#getSummary()} value
*/
- @Deprecated
protected abstract String onGetSummary();
/**
@@ -217,4 +230,12 @@ public abstract class SettingInjectorService extends Service {
* @return the {@link android.preference.Preference#isEnabled()} value
*/
protected abstract boolean onGetEnabled();
+
+ /**
+ * Sends a broadcast to refresh the injected settings on location settings page.
+ */
+ public static final void refreshSettings(Context context) {
+ Intent intent = new Intent(ACTION_INJECTED_SETTING_CHANGED);
+ context.sendBroadcast(intent);
+ }
}
diff --git a/media/Android.bp b/media/Android.bp
new file mode 100644
index 000000000000..d5da6f266952
--- /dev/null
+++ b/media/Android.bp
@@ -0,0 +1,36 @@
+java_library {
+ // TODO: include media2.jar in the media apex and add it to the bootclasspath.
+ name: "media2",
+
+ srcs: [
+ ":media2-srcs",
+ ":framework-media-annotation-srcs",
+ ],
+
+ static_libs: [
+ "mediaplayer2-protos",
+ ],
+
+ // Make sure that the implementaion only relies on SDK or system APIs.
+ sdk_version: "system_current",
+}
+
+filegroup {
+ name: "media2-srcs",
+ srcs: [
+ "java/android/media/CloseGuard.java",
+ "java/android/media/DataSourceCallback.java",
+ "java/android/media/DataSourceDesc.java",
+ "java/android/media/UriDataSourceDesc.java",
+ "java/android/media/FileDataSourceDesc.java",
+ "java/android/media/CallbackDataSourceDesc.java",
+ "java/android/media/VideoSize.java",
+ "java/android/media/Media2Utils.java",
+ "java/android/media/MediaPlayer2Utils.java",
+ "java/android/media/MediaPlayer2.java",
+ "java/android/media/Media2HTTPService.java",
+ "java/android/media/Media2HTTPConnection.java",
+ "java/android/media/RoutingDelegate.java",
+ "java/android/media/BufferingParams.java",
+ ],
+}
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 823af656abaa..fca70747e202 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -254,6 +254,22 @@ public final class AudioPresentation {
mLabels.hashCode());
}
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName() + " ");
+ sb.append("{ presentation id=" + mPresentationId);
+ sb.append(", program id=" + mProgramId);
+ sb.append(", language=" + mLanguage);
+ sb.append(", labels=" + mLabels);
+ sb.append(", mastering indication=" + mMasteringIndication);
+ sb.append(", audio description=" + mAudioDescriptionAvailable);
+ sb.append(", spoken subtitles=" + mSpokenSubtitlesAvailable);
+ sb.append(", dialogue enhancement=" + mDialogueEnhancementAvailable);
+ sb.append(" }");
+ return sb.toString();
+ }
+
/**
* A builder class for creating {@link AudioPresentation} objects.
*/
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 242ae46f4d33..0375de3cc496 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -27,7 +27,6 @@ import android.media.MediaCodecInfo.CodecCapabilities;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.IHwBinder;
import android.os.Looper;
import android.os.Message;
@@ -1427,6 +1426,24 @@ import java.util.concurrent.locks.ReentrantLock;
<td>&#9094;</td>
</tr>
<tr>
+ <td>(29+)</td>
+ <td>29+</td>
+ <td>29+</td>
+ <td>29+</td>
+ <td>(29+)</td>
+ <td>(29+)</td>
+ <td>-</td>
+ <td class=fn>{@link #setAudioPresentation setAudioPresentation}</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
<td>-</td>
<td>-</td>
<td>18+</td>
@@ -3260,6 +3277,19 @@ final public class MediaCodec {
public native final void setVideoScalingMode(@VideoScalingMode int mode);
/**
+ * Sets the audio presentation.
+ * @param presentation see {@link AudioPresentation}. In particular, id should be set.
+ */
+ public void setAudioPresentation(@NonNull AudioPresentation presentation) {
+ if (presentation == null) {
+ throw new IllegalArgumentException("audio presentation is null");
+ }
+ native_setAudioPresentation(presentation.getPresentationId(), presentation.getProgramId());
+ }
+
+ private native void native_setAudioPresentation(int presentationId, int programId);
+
+ /**
* Get the component name. If the codec was created by createDecoderByType
* or createEncoderByType, what component is chosen is not known beforehand.
* @throws IllegalStateException if in the Released state.
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index c91d4d3de442..0fb392bfc0fe 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -18,10 +18,10 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
+
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
@@ -269,8 +269,10 @@ final public class MediaMuxer {
public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2;
/** HEIF media file format*/
public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3;
+ /** Ogg media file format*/
+ public static final int MUXER_OUTPUT_OGG = MUXER_OUTPUT_FIRST + 4;
/** @hide */
- public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF;
+ public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_OGG;
};
/** @hide */
@@ -279,6 +281,7 @@ final public class MediaMuxer {
OutputFormat.MUXER_OUTPUT_WEBM,
OutputFormat.MUXER_OUTPUT_3GPP,
OutputFormat.MUXER_OUTPUT_HEIF,
+ OutputFormat.MUXER_OUTPUT_OGG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Format {}
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index 9038f72f1476..d4b1c7f868cb 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -3960,7 +3960,12 @@ public class MediaPlayer2 implements AutoCloseable
textBounds = new Rect(left, top, right, bottom);
}
}
+ return null;
+ /* TimedText c-tor usage is temporarily commented out.
+ * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track
+ * and remove TimedText path from MediaPlayer2.
return new TimedText(textChars, textBounds);
+ */
}
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index d4bfd6175a09..8ced021b1025 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -22,17 +22,17 @@ import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.hardware.Camera;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.Surface;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -41,8 +41,6 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
-import com.android.internal.annotations.GuardedBy;
-
/**
* Used to record audio and video. The recording control is based on a
* simple state machine (see below).
@@ -450,6 +448,9 @@ public class MediaRecorder implements AudioRouting
/** VP8/VORBIS data in a WEBM container */
public static final int WEBM = 9;
+
+ /** Opus data in a Ogg container */
+ public static final int OGG = 11;
};
/**
@@ -474,6 +475,8 @@ public class MediaRecorder implements AudioRouting
public static final int AAC_ELD = 5;
/** Ogg Vorbis audio codec */
public static final int VORBIS = 6;
+ /** Opus audio codec */
+ public static final int OPUS = 7;
}
/**
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 503720939113..257860890437 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -752,6 +752,13 @@ void JMediaCodec::setVideoScalingMode(int mode) {
}
}
+void JMediaCodec::selectAudioPresentation(const int32_t presentationId, const int32_t programId) {
+ sp<AMessage> msg = new AMessage;
+ msg->setInt32("audio-presentation-presentation-id", presentationId);
+ msg->setInt32("audio-presentation-program-id", programId);
+ (void)mCodec->setParameters(msg);
+}
+
static jthrowable createCodecException(
JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
ScopedLocalRef<jclass> clazz(
@@ -1874,6 +1881,18 @@ static void android_media_MediaCodec_setVideoScalingMode(
codec->setVideoScalingMode(mode);
}
+static void android_media_MediaCodec_setAudioPresentation(
+ JNIEnv *env, jobject thiz, jint presentationId, jint programId) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId);
+}
+
static void android_media_MediaCodec_native_init(JNIEnv *env) {
ScopedLocalRef<jclass> clazz(
env, env->FindClass("android/media/MediaCodec"));
@@ -2183,6 +2202,9 @@ static const JNINativeMethod gMethods[] = {
{ "setVideoScalingMode", "(I)V",
(void *)android_media_MediaCodec_setVideoScalingMode },
+ { "native_setAudioPresentation", "(II)V",
+ (void *)android_media_MediaCodec_setAudioPresentation },
+
{ "native_init", "()V", (void *)android_media_MediaCodec_native_init },
{ "native_setup", "(Ljava/lang/String;ZZ)V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 985f55a89254..0a53f1a0e268 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -128,6 +128,8 @@ struct JMediaCodec : public AHandler {
void setVideoScalingMode(int mode);
+ void selectAudioPresentation(const int32_t presentationId, const int32_t programId);
+
protected:
virtual ~JMediaCodec();
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 693bd8b4b51c..747d4c01867e 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include "android_media_AudioEffect.h"
#include <stdio.h>
@@ -29,6 +28,10 @@
#include <nativehelper/ScopedUtfChars.h>
+#include "android_media_AudioEffect.h"
+#include "android_media_AudioEffectDescriptor.h"
+#include "android_media_AudioErrors.h"
+
using namespace android;
#define AUDIOEFFECT_SUCCESS 0
@@ -49,8 +52,6 @@ struct fields_t {
jmethodID midPostNativeEvent; // event post callback method
jfieldID fidNativeAudioEffect; // stores in Java the native AudioEffect object
jfieldID fidJniData; // stores in Java additional resources used by the native AudioEffect
- jclass clazzDesc; // AudioEffect.Descriptor class
- jmethodID midDescCstor; // AudioEffect.Descriptor class constructor
};
static fields_t fields;
@@ -226,7 +227,6 @@ android_media_AudioEffect_native_init(JNIEnv *env)
ALOGV("android_media_AudioEffect_native_init");
fields.clazzEffect = NULL;
- fields.clazzDesc = NULL;
// Get the AudioEffect class
jclass clazz = env->FindClass(kClassPathName);
@@ -263,23 +263,6 @@ android_media_AudioEffect_native_init(JNIEnv *env)
ALOGE("Can't find AudioEffect.%s", "mJniData");
return;
}
-
- clazz = env->FindClass("android/media/audiofx/AudioEffect$Descriptor");
- if (clazz == NULL) {
- ALOGE("Can't find android/media/audiofx/AudioEffect$Descriptor class");
- return;
- }
- fields.clazzDesc = (jclass)env->NewGlobalRef(clazz);
-
- fields.midDescCstor
- = env->GetMethodID(
- fields.clazzDesc,
- "<init>",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- if (fields.midDescCstor == NULL) {
- ALOGE("Can't find android/media/audiofx/AudioEffect$Descriptor class constructor");
- return;
- }
}
@@ -297,12 +280,6 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t
const char *uuidStr = NULL;
effect_descriptor_t desc;
jobject jdesc;
- char str[EFFECT_STRING_LEN_MAX];
- jstring jdescType;
- jstring jdescUuid;
- jstring jdescConnect;
- jstring jdescName;
- jstring jdescImplementor;
ScopedUtfChars opPackageNameStr(env, opPackageName);
@@ -394,41 +371,12 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t
// get the effect descriptor
desc = lpAudioEffect->descriptor();
- AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX);
- jdescType = env->NewStringUTF(str);
-
- AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX);
- jdescUuid = env->NewStringUTF(str);
-
- if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
- jdescConnect = env->NewStringUTF("Auxiliary");
- } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) {
- jdescConnect = env->NewStringUTF("Pre Processing");
- } else {
- jdescConnect = env->NewStringUTF("Insert");
- }
-
- jdescName = env->NewStringUTF(desc.name);
- jdescImplementor = env->NewStringUTF(desc.implementor);
-
- jdesc = env->NewObject(fields.clazzDesc,
- fields.midDescCstor,
- jdescType,
- jdescUuid,
- jdescConnect,
- jdescName,
- jdescImplementor);
- env->DeleteLocalRef(jdescType);
- env->DeleteLocalRef(jdescUuid);
- env->DeleteLocalRef(jdescConnect);
- env->DeleteLocalRef(jdescName);
- env->DeleteLocalRef(jdescImplementor);
- if (jdesc == NULL) {
- ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
+ if (convertAudioEffectDescriptorFromNative(env, &jdesc, &desc) != AUDIO_JAVA_SUCCESS) {
goto setup_failure;
}
env->SetObjectArrayElement(javadesc, 0, jdesc);
+ env->DeleteLocalRef(jdesc);
setAudioEffect(env, thiz, lpAudioEffect);
@@ -729,23 +677,16 @@ static jobjectArray
android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz __unused)
{
effect_descriptor_t desc;
- char str[EFFECT_STRING_LEN_MAX];
uint32_t totalEffectsCount = 0;
uint32_t returnedEffectsCount = 0;
uint32_t i = 0;
- jstring jdescType;
- jstring jdescUuid;
- jstring jdescConnect;
- jstring jdescName;
- jstring jdescImplementor;
- jobject jdesc;
jobjectArray ret;
if (AudioEffect::queryNumberEffects(&totalEffectsCount) != NO_ERROR) {
return NULL;
}
- jobjectArray temp = env->NewObjectArray(totalEffectsCount, fields.clazzDesc, NULL);
+ jobjectArray temp = env->NewObjectArray(totalEffectsCount, audioEffectDescriptorClass(), NULL);
if (temp == NULL) {
return temp;
}
@@ -757,49 +698,18 @@ android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz __unused
goto queryEffects_failure;
}
- if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
- jdescConnect = env->NewStringUTF("Auxiliary");
- } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_INSERT) {
- jdescConnect = env->NewStringUTF("Insert");
- } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) {
- jdescConnect = env->NewStringUTF("Pre Processing");
- } else {
+ jobject jdesc;
+ if (convertAudioEffectDescriptorFromNative(env, &jdesc, &desc) != AUDIO_JAVA_SUCCESS) {
continue;
}
-
- AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX);
- jdescType = env->NewStringUTF(str);
-
- AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX);
- jdescUuid = env->NewStringUTF(str);
-
- jdescName = env->NewStringUTF(desc.name);
- jdescImplementor = env->NewStringUTF(desc.implementor);
-
- jdesc = env->NewObject(fields.clazzDesc,
- fields.midDescCstor,
- jdescType,
- jdescUuid,
- jdescConnect,
- jdescName,
- jdescImplementor);
- env->DeleteLocalRef(jdescType);
- env->DeleteLocalRef(jdescUuid);
- env->DeleteLocalRef(jdescConnect);
- env->DeleteLocalRef(jdescName);
- env->DeleteLocalRef(jdescImplementor);
- if (jdesc == NULL) {
- ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
- goto queryEffects_failure;
- }
-
env->SetObjectArrayElement(temp, returnedEffectsCount++, jdesc);
- }
+ env->DeleteLocalRef(jdesc);
+ }
if (returnedEffectsCount == 0) {
goto queryEffects_failure;
}
- ret = env->NewObjectArray(returnedEffectsCount, fields.clazzDesc, NULL);
+ ret = env->NewObjectArray(returnedEffectsCount, audioEffectDescriptorClass(), NULL);
if (ret == NULL) {
goto queryEffects_failure;
}
@@ -835,51 +745,11 @@ android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz _
}
ALOGV("queryDefaultPreProcessing() got %d effects", numEffects);
- jobjectArray ret = env->NewObjectArray(numEffects, fields.clazzDesc, NULL);
- if (ret == NULL) {
- return ret;
- }
-
- char str[EFFECT_STRING_LEN_MAX];
- jstring jdescType;
- jstring jdescUuid;
- jstring jdescConnect;
- jstring jdescName;
- jstring jdescImplementor;
- jobject jdesc;
-
- for (uint32_t i = 0; i < numEffects; i++) {
-
- AudioEffect::guidToString(&descriptors[i].type, str, EFFECT_STRING_LEN_MAX);
- jdescType = env->NewStringUTF(str);
- AudioEffect::guidToString(&descriptors[i].uuid, str, EFFECT_STRING_LEN_MAX);
- jdescUuid = env->NewStringUTF(str);
- jdescConnect = env->NewStringUTF("Pre Processing");
- jdescName = env->NewStringUTF(descriptors[i].name);
- jdescImplementor = env->NewStringUTF(descriptors[i].implementor);
-
- jdesc = env->NewObject(fields.clazzDesc,
- fields.midDescCstor,
- jdescType,
- jdescUuid,
- jdescConnect,
- jdescName,
- jdescImplementor);
- env->DeleteLocalRef(jdescType);
- env->DeleteLocalRef(jdescUuid);
- env->DeleteLocalRef(jdescConnect);
- env->DeleteLocalRef(jdescName);
- env->DeleteLocalRef(jdescImplementor);
- if (jdesc == NULL) {
- ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
- env->DeleteLocalRef(ret);
- return NULL;
- }
+ std::vector<effect_descriptor_t> descVector(descriptors.get(), descriptors.get() + numEffects);
- env->SetObjectArrayElement(ret, i, jdesc);
- }
-
- return ret;
+ jobjectArray ret;
+ convertAudioEffectDescriptorVectorFromNative(env, &ret, descVector);
+ return ret;
}
// ----------------------------------------------------------------------------
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index b7d7b0339376..45de36ed3e4d 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -440,6 +440,7 @@ static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz)
if (lpVisualizer == 0) {
return;
}
+ lpVisualizer->release();
}
// delete the JNI data
VisualizerJniStorage* lpJniStorage =
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 96113d68c9b8..537aed498cd2 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -235,6 +235,10 @@ LIBANDROID {
android_getaddrinfofornetwork; # introduced=23
android_setprocnetwork; # introduced=23
android_setsocknetwork; # introduced=23
+ android_res_cancel; # introduced=29
+ android_res_nquery; # introduced=29
+ android_res_nresult; # introduced=29
+ android_res_nsend; # introduced=29
local:
*;
};
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index 9b5a5a1f4b52..be3531da462d 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -1,10 +1,15 @@
-# These functions have been part of the NDK since API 24.
# They are also all available to vendor code.
LIBANDROID_NET {
global:
+ # These functions have been part of the NDK since API 24.
+ android_getaddrinfofornetwork; # vndk
android_setsocknetwork; # vndk
android_setprocnetwork; # vndk
- android_getaddrinfofornetwork; # vndk
+ # These functions have been part of the NDK since API 29.
+ android_res_cancel; # vndk
+ android_res_nquery; # vndk
+ android_res_nresult; # vndk
+ android_res_nsend; # vndk
local:
*;
};
diff --git a/native/android/net.c b/native/android/net.c
index 60296a7bd00c..e32b7875b4e7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -83,3 +83,31 @@ int android_getaddrinfofornetwork(net_handle_t network,
return android_getaddrinfofornet(node, service, hints, netid, 0, res);
}
+
+int android_res_nquery(net_handle_t network,
+ const char *dname, int ns_class, int ns_type) {
+ unsigned netid;
+ if (!getnetidfromhandle(network, &netid)) {
+ return -ENONET;
+ }
+
+ return resNetworkQuery(netid, dname, ns_class, ns_type);
+}
+
+int android_res_nresult(int fd, int *rcode, unsigned char *answer, int anslen) {
+ return resNetworkResult(fd, rcode, answer, anslen);
+}
+
+int android_res_nsend(net_handle_t network,
+ const unsigned char *msg, int msglen) {
+ unsigned netid;
+ if (!getnetidfromhandle(network, &netid)) {
+ return -ENONET;
+ }
+
+ return resNetworkSend(netid, msg, msglen);
+}
+
+void android_res_cancel(int nsend_fd) {
+ resNetworkCancel(nsend_fd);
+}
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index f244f9f88684..74d6605a1ffb 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -26,6 +26,7 @@ android_app {
],
static_libs: [
+ "CarNotificationLib",
"SystemUI-core",
"SystemUIPluginLib",
"SystemUISharedLib",
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 452d61df5322..572737f92370 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -28,4 +28,31 @@
<bool name="config_enableRightNavigationBar">false</bool>
<bool name="config_enableBottomNavigationBar">true</bool>
+ <!-- SystemUI Services: The classes of the stuff to start. This is duplicated from core
+ SystemUi b/c it can't be overlayed at this level for now
+ -->
+ <string-array name="config_systemUIServiceComponents" translatable="false">
+ <item>com.android.systemui.Dependency</item>
+ <item>com.android.systemui.util.NotificationChannels</item>
+ <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
+ <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
+ <item>com.android.systemui.recents.Recents</item>
+ <item>com.android.systemui.volume.VolumeUI</item>
+ <item>com.android.systemui.stackdivider.Divider</item>
+ <item>com.android.systemui.SystemBars</item>
+ <item>com.android.systemui.usb.StorageNotification</item>
+ <item>com.android.systemui.power.PowerUI</item>
+ <item>com.android.systemui.media.RingtonePlayer</item>
+ <item>com.android.systemui.keyboard.KeyboardUI</item>
+ <item>com.android.systemui.pip.PipUI</item>
+ <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
+ <item>@string/config_systemUIVendorServiceComponent</item>
+ <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
+ <item>com.android.systemui.LatencyTester</item>
+ <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
+ <item>com.android.systemui.ScreenDecorations</item>
+ <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
+ <item>com.android.systemui.SliceBroadcastRelayHandler</item>
+ <item>com.android.systemui.notifications.NotificationsUI</item>
+ </string-array>
</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
new file mode 100644
index 000000000000..cb92c4259504
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notifications;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.car.notification.CarNotificationListener;
+import com.android.car.notification.CarUxRestrictionManagerWrapper;
+import com.android.car.notification.NotificationViewController;
+import com.android.car.notification.PreprocessingManager;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+/**
+ * Standalone SystemUI for displaying Notifications that have been designed to be used in the car
+ */
+public class NotificationsUI extends SystemUI {
+
+ private static final String TAG = "NotificationsUI";
+ private CarNotificationListener mCarNotificationListener;
+ private CarUxRestrictionsManager mCarUxRestrictionsManager;
+ private Car mCar;
+ private ViewGroup mCarNotificationWindow;
+ private NotificationViewController mNotificationViewController;
+ private boolean mIsShowing;
+ private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper =
+ new CarUxRestrictionManagerWrapper();
+
+ /**
+ * Inits the window that hosts the notifications and establishes the connections
+ * to the car related services.
+ */
+ @Override
+ public void start() {
+ WindowManager windowManager =
+ (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mCarNotificationListener = new CarNotificationListener();
+ mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper);
+ mCar = Car.createCar(mContext, mCarConnectionListener);
+ mCar.connect();
+
+
+ mCarNotificationWindow = (ViewGroup) View.inflate(mContext,
+ R.layout.navigation_bar_window, null);
+ View.inflate(mContext,
+ com.android.car.notification.R.layout.notification_center_activity,
+ mCarNotificationWindow);
+ mCarNotificationWindow.findViewById(
+ com.android.car.notification.R.id.exit_button_container)
+ .setOnClickListener(v -> toggleShowingCarNotifications());
+
+ WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ layoutParams.setTitle("Car Notification Window");
+ // start in the hidden state
+ mCarNotificationWindow.setVisibility(View.GONE);
+ windowManager.addView(mCarNotificationWindow, layoutParams);
+ mNotificationViewController = new NotificationViewController(
+ mCarNotificationWindow
+ .findViewById(com.android.car.notification.R.id.notification_view),
+ PreprocessingManager.getInstance(mContext),
+ mCarNotificationListener,
+ mCarUxRestrictionManagerWrapper
+ );
+ // Add to the SystemUI component registry
+ putComponent(NotificationsUI.class, this);
+ }
+
+ /**
+ * Connection callback to establish UX Restrictions
+ */
+ private ServiceConnection mCarConnectionListener = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
+ Car.CAR_UX_RESTRICTION_SERVICE);
+ mCarUxRestrictionManagerWrapper
+ .setCarUxRestrictionsManager(mCarUxRestrictionsManager);
+ PreprocessingManager preprocessingManager = PreprocessingManager.getInstance(
+ mContext);
+ preprocessingManager
+ .setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected in CarConnectionListener", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.e(TAG, "Car service disconnected unexpectedly");
+ }
+ };
+
+ /**
+ * Toggles the visiblity of the notifications
+ */
+ public void toggleShowingCarNotifications() {
+ if (mCarNotificationWindow.getVisibility() == View.VISIBLE) {
+ closeCarNotifications();
+ return;
+ }
+ openCarNotifications();
+ }
+
+ /**
+ * Hides the notifications
+ */
+ public void closeCarNotifications() {
+ mCarNotificationWindow.setVisibility(View.GONE);
+ mNotificationViewController.disable();
+ mIsShowing = false;
+ }
+
+ /**
+ * Sets the notifications to visible
+ */
+ public void openCarNotifications() {
+ mCarNotificationWindow.setVisibility(View.VISIBLE);
+ mNotificationViewController.enable();
+ mIsShowing = true;
+ }
+
+ /**
+ * Returns {@code true} if notifications are currently on the screen
+ */
+ public boolean isShowing() {
+ return mIsShowing;
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 81f7846b357d..0cba351a8a9c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -21,7 +21,6 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
-import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -34,7 +33,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController;
*/
class CarNavigationBarView extends LinearLayout {
private View mNavButtons;
- private AlphaOptimizedImageButton mNotificationsButton;
+ private CarFacetButton mNotificationsButton;
private CarStatusBar mCarStatusBar;
private Context mContext;
private View mLockScreenButtons;
@@ -71,7 +70,7 @@ class CarNavigationBarView extends LinearLayout {
}
protected void onNotificationsClick(View v) {
- mCarStatusBar.togglePanel();
+ mCarStatusBar.toggleCarNotifications();
}
/**
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 2d90f8f0afd9..5da236ceb211 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -35,6 +35,7 @@ import com.android.systemui.R;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.notifications.NotificationsUI;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -587,4 +588,9 @@ public class CarStatusBar extends StatusBar implements
private Drawable getDefaultWallpaper() {
return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
}
+
+ public void toggleCarNotifications() {
+ getComponent(NotificationsUI.class).toggleShowingCarNotifications();
+ }
+
}
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index ff70e9712bcc..010a810cd791 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -63,6 +63,13 @@
android:resource="@array/autofill_field_classification_available_algorithms" />
</service>
+ <service android:name=".sms.FinancialSmsServiceImpl"
+ android:permission="android.permission.BIND_FINANCIAL_SMS_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.sms.action.FINANCIAL_SERVICE_INTENT" />
+ </intent-filter>
+ </service>
+
<library android:name="android.ext.services"/>
</application>
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 6f2b6c9dafd4..38df9b0a6fdc 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -18,6 +18,7 @@ package android.ext.services.notification;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.Person;
import android.app.RemoteAction;
import android.content.Context;
import android.os.Bundle;
@@ -31,8 +32,14 @@ import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;
@@ -50,6 +57,8 @@ public class SmartActionsHelper {
private static final int MAX_ACTIONS_PER_LINK = 1;
private static final int MAX_SMART_ACTIONS = 3;
private static final int MAX_SUGGESTED_REPLIES = 3;
+ // TODO: Make this configurable.
+ private static final int MAX_MESSAGES_TO_EXTRACT = 5;
private static final ConversationActions.TypeConfig TYPE_CONFIG =
new ConversationActions.TypeConfig.Builder().setIncludedTypes(
@@ -64,9 +73,6 @@ public class SmartActionsHelper {
/**
* Adds action adjustments based on the notification contents.
- *
- * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions
- * from notification text / message, we can replace most of the code here by consuming that API.
*/
@NonNull
ArrayList<Notification.Action> suggestActions(@Nullable Context context,
@@ -84,9 +90,13 @@ public class SmartActionsHelper {
if (tcm == null) {
return EMPTY_ACTION_LIST;
}
+ List<ConversationActions.Message> messages = extractMessages(entry.getNotification());
+ if (messages.isEmpty()) {
+ return EMPTY_ACTION_LIST;
+ }
+ // TODO: Move to TextClassifier.suggestConversationActions once it is ready.
return suggestActionsFromText(
- tcm,
- getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS);
+ tcm, messages.get(messages.size() - 1).getText(), MAX_SMART_ACTIONS);
}
ArrayList<CharSequence> suggestReplies(@Nullable Context context,
@@ -104,14 +114,12 @@ public class SmartActionsHelper {
if (tcm == null) {
return EMPTY_REPLY_LIST;
}
- CharSequence text = getMostSalientActionText(entry.getNotification());
- ConversationActions.Message message =
- new ConversationActions.Message.Builder()
- .setText(text)
- .build();
-
+ List<ConversationActions.Message> messages = extractMessages(entry.getNotification());
+ if (messages.isEmpty()) {
+ return EMPTY_REPLY_LIST;
+ }
ConversationActions.Request request =
- new ConversationActions.Request.Builder(Collections.singletonList(message))
+ new ConversationActions.Request.Builder(messages)
.setMaxSuggestions(MAX_SUGGESTED_REPLIES)
.setHints(HINTS)
.setTypeConfig(TYPE_CONFIG)
@@ -140,10 +148,6 @@ public class SmartActionsHelper {
if (!Process.myUserHandle().equals(entry.getSbn().getUser())) {
return false;
}
- if (notification.actions != null
- && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) {
- return false;
- }
if ((notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS) != 0) {
return false;
}
@@ -176,21 +180,41 @@ public class SmartActionsHelper {
/** Returns the text most salient for action extraction in a notification. */
@Nullable
- private CharSequence getMostSalientActionText(@NonNull Notification notification) {
- /* If it's messaging style, use the most recent message. */
- // TODO: Use the last few X messages instead and take the Person object into consideration.
+ private List<ConversationActions.Message> extractMessages(@NonNull Notification notification) {
Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
- if (messages != null && messages.length != 0) {
- Bundle lastMessage = (Bundle) messages[messages.length - 1];
- CharSequence lastMessageText =
- lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT);
- if (!TextUtils.isEmpty(lastMessageText)) {
- return lastMessageText;
+ if (messages == null || messages.length == 0) {
+ return Arrays.asList(new ConversationActions.Message.Builder(
+ ConversationActions.Message.PERSON_USER_REMOTE)
+ .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
+ .build());
+ }
+ Person localUser = notification.extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+ Deque<ConversationActions.Message> extractMessages = new ArrayDeque<>();
+ for (int i = messages.length - 1; i >= 0; i--) {
+ Notification.MessagingStyle.Message message =
+ Notification.MessagingStyle.Message.getMessageFromBundle((Bundle) messages[i]);
+ if (message == null) {
+ continue;
+ }
+ Person senderPerson = message.getSenderPerson();
+ // Skip encoding once the sender is missing as it is important to distinguish
+ // local user and remote user when generating replies.
+ if (senderPerson == null) {
+ break;
+ }
+ Person author = localUser != null && localUser.equals(senderPerson)
+ ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson;
+ extractMessages.push(new ConversationActions.Message.Builder(author)
+ .setText(message.getText())
+ .setReferenceTime(
+ ZonedDateTime.ofInstant(Instant.ofEpochMilli(message.getTimestamp()),
+ ZoneOffset.systemDefault()))
+ .build());
+ if (extractMessages.size() >= MAX_MESSAGES_TO_EXTRACT) {
+ break;
}
}
-
- // Fall back to using the normal text.
- return notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+ return new ArrayList<>(extractMessages);
}
/** Returns a list of actions to act on entities in a given piece of text. */
diff --git a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java
new file mode 100644
index 000000000000..ab71802102ae
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ext.services.sms;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Bundle;
+import android.service.sms.FinancialSmsService;
+import android.util.Log;
+
+import java.util.ArrayList;
+/**
+ * Service to provide financial apps access to sms messages.
+ */
+public class FinancialSmsServiceImpl extends FinancialSmsService {
+
+ private static final String TAG = "FinancialSmsServiceImpl";
+ private static final String KEY_COLUMN_NAMES = "column_names";
+
+ @Nullable
+ @Override
+ public CursorWindow onGetSmsMessages(@NonNull Bundle params) {
+ ArrayList<String> columnNames = params.getStringArrayList(KEY_COLUMN_NAMES);
+ if (columnNames == null || columnNames.size() <= 0) {
+ return null;
+ }
+
+ Uri inbox = Uri.parse("content://sms/inbox");
+
+ try (Cursor cursor = getContentResolver().query(inbox, null, null, null, null);
+ CursorWindow window = new CursorWindow("FinancialSmsMessages")) {
+ int messageCount = cursor.getCount();
+ if (messageCount > 0 && cursor.moveToFirst()) {
+ window.setNumColumns(columnNames.size());
+ for (int row = 0; row < messageCount; row++) {
+ if (!window.allocRow()) {
+ Log.e(TAG, "CursorWindow ran out of memory.");
+ return null;
+ }
+ for (int col = 0; col < columnNames.size(); col++) {
+ String columnName = columnNames.get(col);
+ int inboxColumnIndex = cursor.getColumnIndexOrThrow(columnName);
+ String inboxColumnValue = cursor.getString(inboxColumnIndex);
+ boolean addedToCursorWindow = window.putString(inboxColumnValue, row, col);
+ if (!addedToCursorWindow) {
+ Log.e(TAG, "Failed to add:"
+ + inboxColumnValue
+ + ";column:"
+ + columnName);
+ return null;
+ }
+ }
+ cursor.moveToNext();
+ }
+ } else {
+ Log.w(TAG, "No sms messages.");
+ }
+ return window;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get sms messages.");
+ return null;
+ }
+ }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
new file mode 100644
index 000000000000..60d31fca8ddb
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -0,0 +1,236 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ext.services.notification;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.Person;
+import android.content.Context;
+import android.os.Process;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+@RunWith(AndroidJUnit4.class)
+public class SmartActionHelperTest {
+
+ private SmartActionsHelper mSmartActionsHelper = new SmartActionsHelper();
+ private Context mContext;
+ @Mock private TextClassifier mTextClassifier;
+ @Mock private NotificationEntry mNotificationEntry;
+ @Mock private StatusBarNotification mStatusBarNotification;
+ private Notification.Builder mNotificationBuilder;
+ private AssistantSettings mSettings;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ mContext.getSystemService(TextClassificationManager.class)
+ .setTextClassifier(mTextClassifier);
+ when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class)))
+ .thenReturn(new ConversationActions(Collections.emptyList()));
+
+ when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification);
+ // The notification is eligible to have smart suggestions.
+ when(mNotificationEntry.hasInlineReply()).thenReturn(true);
+ when(mNotificationEntry.isMessaging()).thenReturn(true);
+ when(mStatusBarNotification.getPackageName()).thenReturn("random.app");
+ when(mStatusBarNotification.getUser()).thenReturn(Process.myUserHandle());
+ mNotificationBuilder = new Notification.Builder(mContext, "channel");
+ mSettings = AssistantSettings.createForTesting(
+ null, null, Process.myUserHandle().getIdentifier(), null);
+ mSettings.mGenerateActions = true;
+ mSettings.mGenerateReplies = true;
+ }
+
+ @Test
+ public void testSuggestReplies_notMessagingApp() {
+ when(mNotificationEntry.isMessaging()).thenReturn(false);
+ ArrayList<CharSequence> textReplies =
+ mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+ assertThat(textReplies).isEmpty();
+ }
+
+ @Test
+ public void testSuggestReplies_noInlineReply() {
+ when(mNotificationEntry.hasInlineReply()).thenReturn(false);
+ ArrayList<CharSequence> textReplies =
+ mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+ assertThat(textReplies).isEmpty();
+ }
+
+ @Test
+ public void testSuggestReplies_nonMessageStyle() {
+ Notification notification = mNotificationBuilder.setContentText("Where are you?").build();
+ when(mNotificationEntry.getNotification()).thenReturn(notification);
+
+ List<ConversationActions.Message> messages = getMessagesInRequest();
+ assertThat(messages).hasSize(1);
+ MessageSubject.assertThat(messages.get(0)).hasText("Where are you?");
+ }
+
+ @Test
+ public void testSuggestReplies_messageStyle() {
+ Person me = new Person.Builder().setName("Me").build();
+ Person userA = new Person.Builder().setName("A").build();
+ Person userB = new Person.Builder().setName("B").build();
+ Notification.MessagingStyle style =
+ new Notification.MessagingStyle(me)
+ .addMessage("firstMessage", 1000, (Person) null)
+ .addMessage("secondMessage", 2000, me)
+ .addMessage("thirdMessage", 3000, userA)
+ .addMessage("fourthMessage", 4000, userB);
+ Notification notification =
+ mNotificationBuilder
+ .setContentText("You have three new messages")
+ .setStyle(style)
+ .build();
+ when(mNotificationEntry.getNotification()).thenReturn(notification);
+
+ List<ConversationActions.Message> messages = getMessagesInRequest();
+ assertThat(messages).hasSize(3);
+
+ ConversationActions.Message secondMessage = messages.get(0);
+ MessageSubject.assertThat(secondMessage).hasText("secondMessage");
+ MessageSubject.assertThat(secondMessage)
+ .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL);
+ MessageSubject.assertThat(secondMessage)
+ .hasReferenceTime(createZonedDateTimeFromMsUtc(2000));
+
+ ConversationActions.Message thirdMessage = messages.get(1);
+ MessageSubject.assertThat(thirdMessage).hasText("thirdMessage");
+ MessageSubject.assertThat(thirdMessage).hasPerson(userA);
+ MessageSubject.assertThat(thirdMessage)
+ .hasReferenceTime(createZonedDateTimeFromMsUtc(3000));
+
+ ConversationActions.Message fourthMessage = messages.get(2);
+ MessageSubject.assertThat(fourthMessage).hasText("fourthMessage");
+ MessageSubject.assertThat(fourthMessage).hasPerson(userB);
+ MessageSubject.assertThat(fourthMessage)
+ .hasReferenceTime(createZonedDateTimeFromMsUtc(4000));
+ }
+
+ @Test
+ public void testSuggestReplies_messageStyle_noPerson() {
+ Person me = new Person.Builder().setName("Me").build();
+ Notification.MessagingStyle style =
+ new Notification.MessagingStyle(me).addMessage("message", 1000, (Person) null);
+ Notification notification =
+ mNotificationBuilder
+ .setContentText("You have one new message")
+ .setStyle(style)
+ .build();
+ when(mNotificationEntry.getNotification()).thenReturn(notification);
+
+ mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+
+ verify(mTextClassifier, never())
+ .suggestConversationActions(any(ConversationActions.Request.class));
+ }
+
+ private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) {
+ return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneOffset.systemDefault());
+ }
+
+ private List<ConversationActions.Message> getMessagesInRequest() {
+ mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+
+ ArgumentCaptor<ConversationActions.Request> argumentCaptor =
+ ArgumentCaptor.forClass(ConversationActions.Request.class);
+ verify(mTextClassifier).suggestConversationActions(argumentCaptor.capture());
+ ConversationActions.Request request = argumentCaptor.getValue();
+ return request.getConversation();
+ }
+
+ private static final class MessageSubject
+ extends Subject<MessageSubject, ConversationActions.Message> {
+
+ private static final SubjectFactory<MessageSubject, ConversationActions.Message> FACTORY =
+ new SubjectFactory<MessageSubject, ConversationActions.Message>() {
+ @Override
+ public MessageSubject getSubject(
+ @NonNull FailureStrategy failureStrategy,
+ @NonNull ConversationActions.Message subject) {
+ return new MessageSubject(failureStrategy, subject);
+ }
+ };
+
+ private MessageSubject(
+ FailureStrategy failureStrategy, @Nullable ConversationActions.Message subject) {
+ super(failureStrategy, subject);
+ }
+
+ private void hasText(String text) {
+ if (!Objects.equals(text, getSubject().getText().toString())) {
+ failWithBadResults("has text", text, "has", getSubject().getText());
+ }
+ }
+
+ private void hasPerson(Person person) {
+ if (!Objects.equals(person, getSubject().getAuthor())) {
+ failWithBadResults("has author", person, "has", getSubject().getAuthor());
+ }
+ }
+
+ private void hasReferenceTime(ZonedDateTime referenceTime) {
+ if (!Objects.equals(referenceTime, getSubject().getReferenceTime())) {
+ failWithBadResults(
+ "has reference time",
+ referenceTime,
+ "has",
+ getSubject().getReferenceTime());
+ }
+ }
+
+ private static MessageSubject assertThat(ConversationActions.Message message) {
+ return assertAbout(FACTORY).that(message);
+ }
+ }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java
new file mode 100644
index 000000000000..12575a63d0ad
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ext.services.sms;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import org.junit.Test;
+
+/**
+ * Contains the base tests for FinancialSmsServiceImpl.
+ */
+public class FinancialSmsServiceImplTest {
+
+ private final FinancialSmsServiceImpl mService = new FinancialSmsServiceImpl();
+
+ @Test
+ public void testOnGetSmsMessages_nullWithNoParamData() {
+ assertThat(mService.onGetSmsMessages(new Bundle())).isNull();
+ }
+}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 18b86628ea4d..591cf7071e01 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -16,6 +16,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
index 5ecb614e7209..2f8966c0461b 100644
--- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml
+++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
@@ -36,12 +36,23 @@
style="@android:style/TextAppearance.Material.Subhead" />
<CheckBox
- android:id="@+id/checkbox"
+ android:id="@+id/clearContributedFiles"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="-8dp"
android:paddingLeft="8sp"
+ android:visibility="gone"
+ style="@android:style/TextAppearance.Material.Subhead" />
+
+ <CheckBox
+ android:id="@+id/keepData"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="-8dp"
+ android:paddingLeft="8sp"
+ android:visibility="gone"
style="@android:style/TextAppearance.Material.Subhead" />
</LinearLayout> \ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 1d8747a1fda6..1e0ff506cb20 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -121,6 +121,8 @@
<string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
<!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] -->
<string name="uninstall_remove_contributed_files">Also remove <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of associated media files.</string>
+ <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] -->
+ <string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string>
<!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] -->
<string name="uninstalling_notification_channel">Running uninstalls</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index d13bb65604af..63d8c5a82519 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -52,6 +52,7 @@ public class UninstallUninstalling extends Activity implements
static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
static final String EXTRA_CLEAR_CONTRIBUTED_FILES =
"com.android.packageinstaller.extra.CLEAR_CONTRIBUTED_FILES";
+ static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA";
private int mUninstallId;
private ApplicationInfo mAppInfo;
@@ -76,6 +77,7 @@ public class UninstallUninstalling extends Activity implements
false);
boolean clearContributedFiles = getIntent().getBooleanExtra(
EXTRA_CLEAR_CONTRIBUTED_FILES, false);
+ boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false);
UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
// Show dialog, which is the whole UI
@@ -101,6 +103,7 @@ public class UninstallUninstalling extends Activity implements
int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0;
+ flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
try {
ActivityThread.getPackageManager().getPackageInstaller().uninstall(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 0fa8c9a688c7..54194491d140 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -285,7 +285,7 @@ public class UninstallerActivity extends Activity {
fragment.show(ft, "dialog");
}
- public void startUninstallProgress(boolean clearContributedFiles) {
+ public void startUninstallProgress(boolean clearContributedFiles, boolean keepData) {
boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());
@@ -312,6 +312,7 @@ public class UninstallerActivity extends Activity {
newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
newIntent.putExtra(UninstallUninstalling.EXTRA_CLEAR_CONTRIBUTED_FILES,
clearContributedFiles);
+ newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
if (returnResult) {
@@ -362,6 +363,7 @@ public class UninstallerActivity extends Activity {
int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0;
+ flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
ActivityThread.getPackageManager().getPackageInstaller().uninstall(
new VersionedPackage(mDialogInfo.appInfo.packageName,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index e4e12759211d..499da758739e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -16,21 +16,30 @@
package com.android.packageinstaller.handheld;
+import static android.os.storage.StorageManager.convert;
import static android.text.format.Formatter.formatFileSize;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
+import android.app.usage.StorageStats;
+import android.app.usage.StorageStatsManager;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
import android.provider.MediaStore;
+import android.util.Log;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
@@ -43,25 +52,120 @@ import java.util.List;
public class UninstallAlertDialogFragment extends DialogFragment implements
DialogInterface.OnClickListener {
+ private static final String LOG_TAG = UninstallAlertDialogFragment.class.getSimpleName();
- private CheckBox mClearContributedFiles;
+ private @Nullable CheckBox mClearContributedFiles;
+ private @Nullable CheckBox mKeepData;
/**
- * Get number of bytes of the combined files contributed by the package.
+ * Get number of bytes of the files contributed by the package.
*
- * @param pkg The package that might have contibuted files.
+ * @param pkg The package that might have contributed files.
* @param user The user the package belongs to.
*
* @return The number of bytes.
*/
- private long getContributedMediaSize(@NonNull String pkg, @NonNull UserHandle user) {
+ private long getContributedMediaSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
try {
return MediaStore.getContributedMediaSize(getContext(), pkg, user);
} catch (IOException e) {
+ Log.e(LOG_TAG, "Cannot determine amount of contributes files for " + pkg
+ + " (user " + user + ")", e);
return 0;
}
}
+ /**
+ * Get number of bytes of the files contributed by the package.
+ *
+ * @param pkg The package that might have contributed files.
+ * @param user The user the package belongs to or {@code null} if files of all users should be
+ * counted.
+ *
+ * @return The number of bytes.
+ */
+ private long getContributedMediaSize(@NonNull String pkg, @Nullable UserHandle user) {
+ UserManager userManager = getContext().getSystemService(UserManager.class);
+
+ long contributedFileSize = 0;
+
+ if (user == null) {
+ List<UserInfo> users = userManager.getUsers();
+
+ int numUsers = users.size();
+ for (int i = 0; i < numUsers; i++) {
+ contributedFileSize += getContributedMediaSizeForUser(pkg,
+ UserHandle.of(users.get(i).id));
+ }
+ } else {
+ contributedFileSize = getContributedMediaSizeForUser(pkg, user);
+ }
+
+ return contributedFileSize;
+ }
+
+ /**
+ * Get number of bytes of the app data of the package.
+ *
+ * @param pkg The package that might have app data.
+ * @param user The user the package belongs to
+ *
+ * @return The number of bytes.
+ */
+ private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
+ StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+ StorageStatsManager storageStatsManager =
+ getContext().getSystemService(StorageStatsManager.class);
+
+ List<StorageVolume> volumes = storageManager.getStorageVolumes();
+ long appDataSize = 0;
+
+ int numVolumes = volumes.size();
+ for (int i = 0; i < numVolumes; i++) {
+ StorageStats stats;
+ try {
+ stats = storageStatsManager.queryStatsForPackage(convert(volumes.get(i).getUuid()),
+ pkg, user);
+ } catch (PackageManager.NameNotFoundException | IOException e) {
+ Log.e(LOG_TAG, "Cannot determine amount of app data for " + pkg + " on "
+ + volumes.get(i) + " (user " + user + ")", e);
+ continue;
+ }
+
+ appDataSize += stats.getDataBytes();
+ }
+
+ return appDataSize;
+ }
+
+ /**
+ * Get number of bytes of the app data of the package.
+ *
+ * @param pkg The package that might have app data.
+ * @param user The user the package belongs to or {@code null} if files of all users should be
+ * counted.
+ *
+ * @return The number of bytes.
+ */
+ private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
+ UserManager userManager = getContext().getSystemService(UserManager.class);
+
+ long appDataSize = 0;
+
+ if (user == null) {
+ List<UserInfo> users = userManager.getUsers();
+
+ int numUsers = users.size();
+ for (int i = 0; i < numUsers; i++) {
+ appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id));
+ }
+ } else {
+ appDataSize = getAppDataSizeForUser(pkg, user);
+ }
+
+ return appDataSize;
+ }
+
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final PackageManager pm = getActivity().getPackageManager();
@@ -108,30 +212,46 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
dialogBuilder.setNegativeButton(android.R.string.cancel, this);
String pkg = dialogInfo.appInfo.packageName;
- long contributedFileSize = 0;
- if (dialogInfo.allUsers) {
- List<UserInfo> users = userManager.getUsers();
+ long contributedFileSize = getContributedMediaSize(pkg,
+ dialogInfo.allUsers ? null : dialogInfo.user);
- int numUsers = users.size();
- for (int i = 0; i < numUsers; i++) {
- UserHandle user = UserHandle.of(users.get(i).id);
+ boolean suggestToKeepAppData;
+ try {
+ PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0);
- contributedFileSize += getContributedMediaSize(pkg, user);
- }
- } else {
- contributedFileSize = getContributedMediaSize(pkg, dialogInfo.user);
+ suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e);
+ suggestToKeepAppData = false;
+ }
+
+ long appDataSize = 0;
+ if (suggestToKeepAppData) {
+ appDataSize = getAppDataSize(pkg, dialogInfo.allUsers ? null : dialogInfo.user);
}
- if (contributedFileSize == 0) {
+ if (contributedFileSize == 0 && appDataSize == 0) {
dialogBuilder.setMessage(messageBuilder.toString());
} else {
LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class);
ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null);
((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString());
- mClearContributedFiles = content.requireViewById(R.id.checkbox);
- mClearContributedFiles.setText(getString(R.string.uninstall_remove_contributed_files,
- formatFileSize(getContext(), contributedFileSize)));
+
+ if (contributedFileSize != 0) {
+ mClearContributedFiles = content.requireViewById(R.id.clearContributedFiles);
+ mClearContributedFiles.setVisibility(View.VISIBLE);
+ mClearContributedFiles.setText(
+ getString(R.string.uninstall_remove_contributed_files,
+ formatFileSize(getContext(), contributedFileSize)));
+ }
+
+ if (appDataSize != 0) {
+ mKeepData = content.requireViewById(R.id.keepData);
+ mKeepData.setVisibility(View.VISIBLE);
+ mKeepData.setText(getString(R.string.uninstall_keep_data,
+ formatFileSize(getContext(), appDataSize)));
+ }
dialogBuilder.setView(content);
}
@@ -143,7 +263,8 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
((UninstallerActivity) getActivity()).startUninstallProgress(
- mClearContributedFiles != null && mClearContributedFiles.isChecked());
+ mClearContributedFiles != null && mClearContributedFiles.isChecked(),
+ mKeepData != null && mKeepData.isChecked());
} else {
((UninstallerActivity) getActivity()).dispatchAborted();
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 21d25f5b030f..ac5fd76f5bda 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -99,7 +99,7 @@ public class UninstallAlertFragment extends GuidedStepFragment {
public void onGuidedActionClicked(GuidedAction action) {
if (isAdded()) {
if (action.getId() == GuidedAction.ACTION_ID_OK) {
- ((UninstallerActivity) getActivity()).startUninstallProgress(false);
+ ((UninstallerActivity) getActivity()).startUninstallProgress(false, false);
getActivity().finish();
} else {
((UninstallerActivity) getActivity()).dispatchAborted();
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2823149a1585..842779d494cd 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -831,6 +831,10 @@
<!-- Toast message shown when setting a new local backup password fails due to the user not supplying the correct existing password. The phrasing here is deliberately quite general. [CHAR LIMIT=80] -->
<string name="local_backup_password_toast_validation_failure">Failure setting backup password</string>
+ <!-- [CHAR LIMIT=30] Location mode screen, temporary summary text to show as the status of a location
+ setting injected by an external app while the app is being queried for the actual value -->
+ <string name="loading_injected_setting_summary">Loading\u2026</string>
+
<!-- Name of each color mode for the display. [CHAR LIMIT=40] -->
<string-array name="color_mode_names">
<item>Vibrant (default)</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index 780fcbab9822..74057be8434b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -37,6 +37,7 @@ import android.os.Messenger;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.IconDrawableFactory;
import android.util.Log;
@@ -44,11 +45,16 @@ import android.util.Xml;
import androidx.preference.Preference;
+import com.android.settingslib.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -102,7 +108,7 @@ public class SettingsInjector {
public SettingsInjector(Context context) {
mContext = context;
mSettings = new HashSet<Setting>();
- mHandler = new StatusLoadingHandler();
+ mHandler = new StatusLoadingHandler(mSettings);
}
/**
@@ -165,7 +171,7 @@ public class SettingsInjector {
Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e);
}
preference.setTitle(setting.title);
- preference.setSummary(null);
+ preference.setSummary(R.string.loading_injected_setting_summary);
preference.setIcon(appIcon);
preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting));
}
@@ -180,6 +186,7 @@ public class SettingsInjector {
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
final List<UserHandle> profiles = um.getUserProfiles();
ArrayList<Preference> prefs = new ArrayList<>();
+ mSettings.clear();
for (UserHandle userHandle : profiles) {
if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
Iterable<InjectedSetting> settings = getSettings(userHandle);
@@ -363,31 +370,28 @@ public class SettingsInjector {
* SettingInjectorService}, so to reduce memory pressure we don't want to load too many at
* once.
*/
- private final class StatusLoadingHandler extends Handler {
+ private static final class StatusLoadingHandler extends Handler {
+ /**
+ * References all the injected settings.
+ */
+ WeakReference<Set<Setting>> mAllSettings;
/**
* Settings whose status values need to be loaded. A set is used to prevent redundant loads.
*/
- private Set<Setting> mSettingsToLoad = new HashSet<Setting>();
+ private Deque<Setting> mSettingsToLoad = new ArrayDeque<Setting>();
/**
* Settings that are being loaded now and haven't timed out. In practice this should have
* zero or one elements.
*/
- private Set<Setting> mSettingsBeingLoaded = new HashSet<Setting>();
-
- /**
- * Settings that are being loaded but have timed out. If only one setting has timed out, we
- * will go ahead and start loading the next setting so that one slow load won't delay the
- * load of the other settings.
- */
- private Set<Setting> mTimedOutSettings = new HashSet<Setting>();
-
- private boolean mReloadRequested;
+ private Set<Setting> mSettingsBeingLoaded = new ArraySet<Setting>();
- private StatusLoadingHandler() {
+ public StatusLoadingHandler(Set<Setting> allSettings) {
super(Looper.getMainLooper());
+ mAllSettings = new WeakReference<>(allSettings);
}
+
@Override
public void handleMessage(Message msg) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -396,20 +400,24 @@ public class SettingsInjector {
// Update state in response to message
switch (msg.what) {
- case WHAT_RELOAD:
- mReloadRequested = true;
+ case WHAT_RELOAD: {
+ final Set<Setting> allSettings = mAllSettings.get();
+ if (allSettings != null) {
+ // Reload requested, so must reload all settings
+ mSettingsToLoad.clear();
+ mSettingsToLoad.addAll(allSettings);
+ }
break;
+ }
case WHAT_RECEIVED_STATUS:
final Setting receivedSetting = (Setting) msg.obj;
receivedSetting.maybeLogElapsedTime();
mSettingsBeingLoaded.remove(receivedSetting);
- mTimedOutSettings.remove(receivedSetting);
removeMessages(WHAT_TIMEOUT, receivedSetting);
break;
case WHAT_TIMEOUT:
final Setting timedOutSetting = (Setting) msg.obj;
mSettingsBeingLoaded.remove(timedOutSetting);
- mTimedOutSettings.add(timedOutSetting);
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Timed out after " + timedOutSetting.getElapsedTime()
+ " millis trying to get status for: " + timedOutSetting);
@@ -421,37 +429,22 @@ public class SettingsInjector {
// Decide whether to load additional settings based on the new state. Start by seeing
// if we have headroom to load another setting.
- if (mSettingsBeingLoaded.size() > 0 || mTimedOutSettings.size() > 1) {
+ if (mSettingsBeingLoaded.size() > 0) {
// Don't load any more settings until one of the pending settings has completed.
- // To reduce memory pressure, we want to be loading at most one setting (plus at
- // most one timed-out setting) at a time. This means we'll be responsible for
- // bringing in at most two services.
+ // To reduce memory pressure, we want to be loading at most one setting.
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "too many services already live for " + msg + ", " + this);
}
return;
}
- if (mReloadRequested && mSettingsToLoad.isEmpty() && mSettingsBeingLoaded.isEmpty()
- && mTimedOutSettings.isEmpty()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "reloading because idle and reload requesteed " + msg + ", " + this);
- }
- // Reload requested, so must reload all settings
- mSettingsToLoad.addAll(mSettings);
- mReloadRequested = false;
- }
-
- // Remove the next setting to load from the queue, if any
- Iterator<Setting> iter = mSettingsToLoad.iterator();
- if (!iter.hasNext()) {
+ if (mSettingsToLoad.isEmpty()) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "nothing left to do for " + msg + ", " + this);
}
return;
}
- Setting setting = iter.next();
- iter.remove();
+ Setting setting = mSettingsToLoad.removeFirst();
// Request the status value
setting.startService();
@@ -473,21 +466,48 @@ public class SettingsInjector {
return "StatusLoadingHandler{" +
"mSettingsToLoad=" + mSettingsToLoad +
", mSettingsBeingLoaded=" + mSettingsBeingLoaded +
- ", mTimedOutSettings=" + mTimedOutSettings +
- ", mReloadRequested=" + mReloadRequested +
'}';
}
}
+ private static class MessengerHandler extends Handler {
+ private WeakReference<Setting> mSettingRef;
+ private Handler mHandler;
+
+ public MessengerHandler(Setting setting, Handler handler) {
+ mSettingRef = new WeakReference(setting);
+ mHandler = handler;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final Setting setting = mSettingRef.get();
+ if (setting == null) {
+ return;
+ }
+ final Preference preference = setting.preference;
+ Bundle bundle = msg.getData();
+ boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
+ String summary = bundle.getString(SettingInjectorService.SUMMARY_KEY, null);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
+ }
+ preference.setSummary(summary);
+ preference.setEnabled(enabled);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(WHAT_RECEIVED_STATUS, setting));
+ }
+ }
+
/**
* Represents an injected setting and the corresponding preference.
*/
protected final class Setting {
-
public final InjectedSetting setting;
public final Preference preference;
public long startMillis;
+
public Setting(InjectedSetting setting, Preference preference) {
this.setting = setting;
this.preference = preference;
@@ -502,20 +522,6 @@ public class SettingsInjector {
}
/**
- * Returns true if they both have the same {@link #setting} value. Ignores mutable
- * {@link #preference} and {@link #startMillis} so that it's safe to use in sets.
- */
- @Override
- public boolean equals(Object o) {
- return this == o || o instanceof Setting && setting.equals(((Setting) o).setting);
- }
-
- @Override
- public int hashCode() {
- return setting.hashCode();
- }
-
- /**
* Starts the service to fetch for the current status for the setting, and updates the
* preference when the service replies.
*/
@@ -529,20 +535,7 @@ public class SettingsInjector {
}
return;
}
- Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- Bundle bundle = msg.getData();
- boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
- }
- preference.setSummary(null);
- preference.setEnabled(enabled);
- mHandler.sendMessage(
- mHandler.obtainMessage(WHAT_RECEIVED_STATUS, Setting.this));
- }
- };
+ Handler handler = new MessengerHandler(this, mHandler);
Messenger messenger = new Messenger(handler);
Intent intent = setting.getServiceIntent();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
index 4a8ef1e5d17c..924eb047c340 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -36,12 +36,12 @@ public class ShadowActivityManager {
}
@Implementation
- public static int getCurrentUser() {
+ protected static int getCurrentUser() {
return sCurrentUserId;
}
@Implementation
- public boolean switchUser(int userId) {
+ protected boolean switchUser(int userId) {
mUserSwitchedTo = userId;
return true;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
index cae74c888f0a..906dba487734 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
@@ -32,7 +32,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto
private BluetoothProfile.ServiceListener mServiceListener;
@Implementation
- public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
+ protected boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
int profile) {
mServiceListener = listener;
return true;
@@ -43,7 +43,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto
}
@Implementation
- public List<Integer> getSupportedProfiles() {
+ protected List<Integer> getSupportedProfiles() {
return mSupportedProfiles;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index 3e91641a69ae..d8fc8613d861 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -34,7 +34,7 @@ public class ShadowDefaultDialerManager {
}
@Implementation
- public static String getDefaultDialerApplication(Context context) {
+ protected static String getDefaultDialerApplication(Context context) {
return sDefaultDialer;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
index dd7b007ca30b..c8c4526e66ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
@@ -36,7 +36,7 @@ public class ShadowSmsApplication {
}
@Implementation
- public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
+ protected static ComponentName getDefaultSmsApplication(Context context, boolean update) {
return sDefaultSmsApplication;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
index a81e39501a8e..c50d646c0861 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -21,11 +21,8 @@ import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserManager;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.List;
@@ -56,5 +53,4 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager
protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
return getProfiles();
}
-
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java
deleted file mode 100644
index 3455765ce24c..000000000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.testutils.shadow;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-
-import com.android.internal.util.XmlUtils;
-
-import org.robolectric.Robolectric;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.util.ReflectionHelpers;
-
-@Implements(XmlUtils.class)
-public class ShadowXmlUtils {
-
- @Implementation
- public static final int convertValueToInt(CharSequence charSeq, int defaultValue) {
- final Class<?> xmlUtilsClass = ReflectionHelpers.loadClass(
- Robolectric.class.getClassLoader(), "com.android.internal.util.XmlUtils");
- try {
- return directlyOn(xmlUtilsClass, "convertValueToInt",
- ReflectionHelpers.ClassParameter.from(CharSequence.class, charSeq),
- ReflectionHelpers.ClassParameter.from(int.class, new Integer(defaultValue)));
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-} \ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
index 9a169d2663de..83cc39a8a1a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
@@ -78,8 +78,8 @@ public class UserManagerHelperRoboTest {
@Test
public void getForegroundUserInfo() {
ShadowActivityManager.setCurrentUser(17);
- when(mUserManager.getUserInfo(ShadowActivityManager.getCurrentUser()))
- .thenReturn(createUserInfoForId(ShadowActivityManager.getCurrentUser()));
+ when(mUserManager.getUserInfo(ActivityManager.getCurrentUser()))
+ .thenReturn(createUserInfoForId(ActivityManager.getCurrentUser()));
assertThat(mHelper.getForegroundUserInfo().id).isEqualTo(17);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
index 88fef08bfcb7..97de7ef2378a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
@@ -18,9 +18,9 @@ package com.android.settingslib.widget;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index ba69f3bb1bc2..9391737fe23c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -218,88 +218,7 @@ public class NotificationRemoteInputManager implements Dumpable {
return false;
}
- ViewParent p = view.getParent();
- RemoteInputView riv = null;
- while (p != null) {
- if (p instanceof View) {
- View pv = (View) p;
- if (pv.isRootNamespace()) {
- riv = findRemoteInputView(pv);
- break;
- }
- }
- p = p.getParent();
- }
- ExpandableNotificationRow row = null;
- while (p != null) {
- if (p instanceof ExpandableNotificationRow) {
- row = (ExpandableNotificationRow) p;
- break;
- }
- p = p.getParent();
- }
-
- if (row == null) {
- return false;
- }
-
- row.setUserExpanded(true);
-
- if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
- final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
- if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
- mCallback.onLockedRemoteInput(row, view);
- return true;
- }
- if (mUserManager.getUserInfo(userId).isManagedProfile()
- && mKeyguardManager.isDeviceLocked(userId)) {
- mCallback.onLockedWorkRemoteInput(userId, row, view);
- return true;
- }
- }
-
- if (riv == null) {
- riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
- if (riv == null) {
- return false;
- }
- if (!row.getPrivateLayout().getExpandedChild().isShown()) {
- mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
- return true;
- }
- }
-
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
- riv.setRevealParameters(cx, cy, r);
- riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(inputs, input);
- riv.focusAnimated();
-
- return true;
- }
-
- private RemoteInputView findRemoteInputView(View v) {
- if (v == null) {
- return null;
- }
- return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+ return activateRemoteInput(view, inputs, input, pendingIntent);
}
};
@@ -355,6 +274,102 @@ public class NotificationRemoteInputManager implements Dumpable {
}
/**
+ * Activates a given {@link RemoteInput}
+ *
+ * @param view The view of the action button or suggestion chip that was tapped.
+ * @param inputs The remote inputs that need to be sent to the app.
+ * @param input The remote input that needs to be activated.
+ * @param pendingIntent The pending intent to be sent to the app.
+ * @return Whether the {@link RemoteInput} was activated.
+ */
+ public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input,
+ PendingIntent pendingIntent) {
+
+ ViewParent p = view.getParent();
+ RemoteInputView riv = null;
+ while (p != null) {
+ if (p instanceof View) {
+ View pv = (View) p;
+ if (pv.isRootNamespace()) {
+ riv = findRemoteInputView(pv);
+ break;
+ }
+ }
+ p = p.getParent();
+ }
+ ExpandableNotificationRow row = null;
+ while (p != null) {
+ if (p instanceof ExpandableNotificationRow) {
+ row = (ExpandableNotificationRow) p;
+ break;
+ }
+ p = p.getParent();
+ }
+
+ if (row == null) {
+ return false;
+ }
+
+ row.setUserExpanded(true);
+
+ if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
+ final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+ if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
+ mCallback.onLockedRemoteInput(row, view);
+ return true;
+ }
+ if (mUserManager.getUserInfo(userId).isManagedProfile()
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ mCallback.onLockedWorkRemoteInput(userId, row, view);
+ return true;
+ }
+ }
+
+ if (riv == null) {
+ riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
+ if (riv == null) {
+ return false;
+ }
+ if (!row.getPrivateLayout().getExpandedChild().isShown()) {
+ mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
+ return true;
+ }
+ }
+
+ int width = view.getWidth();
+ if (view instanceof TextView) {
+ // Center the reveal on the text which might be off-center from the TextView
+ TextView tv = (TextView) view;
+ if (tv.getLayout() != null) {
+ int innerWidth = (int) tv.getLayout().getLineWidth(0);
+ innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+ width = Math.min(width, innerWidth);
+ }
+ }
+ int cx = view.getLeft() + width / 2;
+ int cy = view.getTop() + view.getHeight() / 2;
+ int w = riv.getWidth();
+ int h = riv.getHeight();
+ int r = Math.max(
+ Math.max(cx + cy, cx + (h - cy)),
+ Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+ riv.setRevealParameters(cx, cy, r);
+ riv.setPendingIntent(pendingIntent);
+ riv.setRemoteInput(inputs, input);
+ riv.focusAnimated();
+
+ return true;
+ }
+
+ private RemoteInputView findRemoteInputView(View v) {
+ if (v == null) {
+ return null;
+ }
+ return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+ }
+
+ /**
* Adds all the notification lifetime extenders. Each extender represents a reason for the
* NotificationRemoteInputManager to keep a notification lifetime extended.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index a2a11bbfd650..c7e4d340b7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -105,10 +105,22 @@ public class NotificationPanelView extends PanelView implements
private static final boolean DEBUG = false;
- private static final boolean EXPAND_ON_WAKE_UP = SystemProperties.getBoolean(
+ /**
+ * If passive interrupts expand the NSSL or not
+ */
+ private static final boolean EXPAND_ON_PASSIVE_INTERRUPT = SystemProperties.getBoolean(
"persist.sysui.expand_shade_on_wake_up", true);
+ /**
+ * If the notification panel should remain collapsed when the phone wakes up, even if the user
+ * presses power.
+ */
+ private static final boolean NEVER_EXPAND_WHEN_WAKING_UP = SystemProperties.getBoolean(
+ "persist.sysui.defer_notifications_on_lock_screen", false);
+ /**
+ * If waking up the phone should take you to SHADE_LOCKED instead of KEYGUARD
+ */
private static final boolean WAKE_UP_TO_SHADE = SystemProperties.getBoolean(
- "persist.sysui.go_to_shade_on_wake_up", true);
+ "persist.sysui.go_to_shade_on_wake_up", false);
/**
* Fling expanding QS.
@@ -2774,10 +2786,12 @@ public class NotificationPanelView extends PanelView implements
}
public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation,
- boolean passiveInterrupted) {
+ boolean passivelyInterrupted) {
if (dozing == mDozing) return;
mDozing = dozing;
- mSemiAwake = !EXPAND_ON_WAKE_UP && !mDozing && passiveInterrupted;
+ boolean doNotExpand = (!EXPAND_ON_PASSIVE_INTERRUPT && passivelyInterrupted)
+ || NEVER_EXPAND_WHEN_WAKING_UP;
+ mSemiAwake = doNotExpand && !mDozing;
if (!mSemiAwake) {
mNotificationStackScroller.setDark(mDozing, animate, wakeUpTouchLocation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 913b2ae90cf6..4fa8321fa1e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -36,6 +36,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -63,6 +64,7 @@ public class SmartReplyView extends ViewGroup {
private final SmartReplyConstants mConstants;
private final KeyguardDismissUtil mKeyguardDismissUtil;
+ private final NotificationRemoteInputManager mRemoteInputManager;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
/**
@@ -112,6 +114,7 @@ public class SmartReplyView extends ViewGroup {
super(context, attrs);
mConstants = Dependency.get(SmartReplyConstants.class);
mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class);
+ mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.smart_reply_button_max_height);
@@ -242,12 +245,22 @@ public class SmartReplyView extends ViewGroup {
b.setText(choice);
OnDismissAction action = () -> {
+ // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags.
+ if (smartReplies.remoteInput.getEditChoicesBeforeSending()
+ == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) {
+ entry.remoteInputText = choice;
+ mRemoteInputManager.activateRemoteInput(b,
+ new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
+ smartReplies.pendingIntent);
+ return false;
+ }
+
smartReplyController.smartReplySent(
entry, replyIndex, b.getText(), smartReplies.fromAssistant);
Bundle results = new Bundle();
results.putString(smartReplies.remoteInput.getResultKey(), choice.toString());
Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(new RemoteInput[]{smartReplies.remoteInput}, intent,
+ RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent,
results);
RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
entry.setHasSentReply();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d5deccef412d..36ca52a6e600 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2466,6 +2466,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
/**
+ * AIDL-exposed method. System only.
+ * Gets accessibility window id from window token.
+ *
+ * @param windowToken Window token to get accessibility window id.
+ * @return Accessibility window id for the window token. Returns -1 if no such token is
+ * registered.
+ */
+ @Override
+ public int getAccessibilityWindowId(IBinder windowToken) {
+ synchronized (mLock) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+ throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId");
+ }
+
+ return findWindowIdLocked(windowToken);
+ }
+ }
+
+ /**
* Get the recommended timeout of interactive controls and non-interactive controls.
*
* @return A long for pair of {@code int}s. First integer for interactive one, and second
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 854c03f128dd..08034f734bea 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -48,6 +48,7 @@ import android.content.pm.PermissionInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -1306,8 +1307,12 @@ class AlarmManagerService extends SystemService {
// because kernel doesn't keep this after reboot
setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
- // Also sure that we're booting with a halfway sensible current time
- final long systemBuildTime = Environment.getRootDirectory().lastModified();
+ // Ensure that we're booting with a halfway sensible current time. Use the
+ // most recent of Build.TIME, the root file system's timestamp, and the
+ // value of the ro.build.date.utc system property (which is in seconds).
+ final long systemBuildTime = Long.max(
+ 1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
+ Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
+ ", advancing to build time " + systemBuildTime);
diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java
index d56492521276..8c5ee7f35027 100644
--- a/services/core/java/com/android/server/AnyMotionDetector.java
+++ b/services/core/java/com/android/server/AnyMotionDetector.java
@@ -26,8 +26,6 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Slog;
-import java.lang.Float;
-
/**
* Determines if the device has been set upon a stationary object.
*/
@@ -140,6 +138,13 @@ public class AnyMotionDetector {
}
}
+ /**
+ * If we do not have an accelerometer, we are not going to collect much data.
+ */
+ public boolean hasSensor() {
+ return mAccelSensor != null;
+ }
+
/*
* Acquire accel data until we determine AnyMotion status.
*/
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index af9d4c8c69b6..08cb7a2f5047 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -43,6 +43,7 @@ import android.net.ConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.NetworkInfo;
import android.net.Uri;
+import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -272,6 +273,7 @@ public class DeviceIdleController extends SystemService
private PowerManager mPowerManager;
private INetworkPolicyManager mNetworkPolicyManager;
private SensorManager mSensorManager;
+ private final boolean mUseMotionSensor;
private Sensor mMotionSensor;
private LocationRequest mLocationRequest;
private Intent mIdleIntent;
@@ -520,9 +522,10 @@ public class DeviceIdleController extends SystemService
updateConnectivityState(intent);
} break;
case Intent.ACTION_BATTERY_CHANGED: {
+ boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+ boolean plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
synchronized (DeviceIdleController.this) {
- int plugged = intent.getIntExtra("plugged", 0);
- updateChargingLocked(plugged != 0);
+ updateChargingLocked(present && plugged);
}
} break;
case Intent.ACTION_PACKAGE_REMOVED: {
@@ -1629,6 +1632,9 @@ public class DeviceIdleController extends SystemService
mHandler = mInjector.getHandler(this);
mAppStateTracker = mInjector.getAppStateTracker(context, FgThread.get().getLooper());
LocalServices.addService(AppStateTracker.class, mAppStateTracker);
+
+ mUseMotionSensor = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_autoPowerModeUseMotionSensor);
}
public DeviceIdleController(Context context) {
@@ -1729,20 +1735,23 @@ public class DeviceIdleController extends SystemService
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
- int sigMotionSensorId = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
- if (sigMotionSensorId > 0) {
- mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
- }
- if (mMotionSensor == null && getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
- mMotionSensor = mSensorManager.getDefaultSensor(
- Sensor.TYPE_WRIST_TILT_GESTURE, true);
- }
- if (mMotionSensor == null) {
- // As a last ditch, fall back to SMD.
- mMotionSensor = mSensorManager.getDefaultSensor(
- Sensor.TYPE_SIGNIFICANT_MOTION, true);
+
+ if (mUseMotionSensor) {
+ int sigMotionSensorId = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
+ if (sigMotionSensorId > 0) {
+ mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
+ }
+ if (mMotionSensor == null && getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
+ mMotionSensor = mSensorManager.getDefaultSensor(
+ Sensor.TYPE_WRIST_TILT_GESTURE, true);
+ }
+ if (mMotionSensor == null) {
+ // As a last ditch, fall back to SMD.
+ mMotionSensor = mSensorManager.getDefaultSensor(
+ Sensor.TYPE_SIGNIFICANT_MOTION, true);
+ }
}
if (getContext().getResources().getBoolean(
@@ -2588,14 +2597,21 @@ public class DeviceIdleController extends SystemService
mState = STATE_SENSING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
EventLogTags.writeDeviceIdle(mState, reason);
- scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
cancelLocatingLocked();
- mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
- mAnyMotionDetector.checkForAnyMotion();
- break;
+
+ // If we have an accelerometer, wait to find out whether we are moving.
+ if (mUseMotionSensor && mAnyMotionDetector.hasSensor()) {
+ scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
+ mNotMoving = false;
+ mAnyMotionDetector.checkForAnyMotion();
+ break;
+ }
+
+ mNotMoving = true;
+ // Otherwise, fall through and check this off the list of requirements.
case STATE_SENSING:
cancelSensingTimeoutAlarmLocked();
mState = STATE_LOCATING;
@@ -2893,9 +2909,12 @@ public class DeviceIdleController extends SystemService
void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
- if (mMotionSensor == null && !(mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE
- || mState == STATE_IDLE_MAINTENANCE)) {
- // If there is no motion sensor on this device, then we won't schedule
+
+ if (mUseMotionSensor && mMotionSensor == null
+ && mState != STATE_QUICK_DOZE_DELAY
+ && mState != STATE_IDLE
+ && mState != STATE_IDLE_MAINTENANCE) {
+ // If there is no motion sensor on this device, but we need one, then we won't schedule
// alarms, because we can't determine if the device is not moving. This effectively
// turns off normal execution of device idling, although it is still possible to
// manually poke it by pretending like the alarm is going off.
@@ -3765,13 +3784,20 @@ public class DeviceIdleController extends SystemService
pw.print(" mLightEnabled="); pw.print(mLightEnabled);
pw.print(" mDeepEnabled="); pw.println(mDeepEnabled);
pw.print(" mForceIdle="); pw.println(mForceIdle);
- pw.print(" mMotionSensor="); pw.println(mMotionSensor);
+ pw.print(" mUseMotionSensor="); pw.print(mUseMotionSensor);
+ if (mUseMotionSensor) {
+ pw.print(" mMotionSensor="); pw.println(mMotionSensor);
+ } else {
+ pw.println();
+ }
pw.print(" mScreenOn="); pw.println(mScreenOn);
pw.print(" mScreenLocked="); pw.println(mScreenLocked);
pw.print(" mNetworkConnected="); pw.println(mNetworkConnected);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mMotionActive="); pw.println(mMotionListener.active);
- pw.print(" mNotMoving="); pw.println(mNotMoving);
+ if (mUseMotionSensor) {
+ pw.print(" mNotMoving="); pw.println(mNotMoving);
+ }
pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
pw.print(mHasGps); pw.print(" mHasNetwork=");
pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated);
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index fe9f1b5e82d6..a9c38bcf2532 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -3,3 +3,4 @@ per-file ConnectivityService.java,NetworkManagementService.java,NsdService.java
# Vibrator / Threads
per-file VibratorService.java, DisplayThread.java = michaelwr@google.com
+per-file VibratorService.java, DisplayThread.java = ogunwale@google.com
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4b092b299029..5901ece7d89e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -16,6 +16,11 @@
package com.android.server;
+import static android.Manifest.permission.INSTALL_PACKAGES;
+import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
@@ -51,6 +56,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -116,6 +122,7 @@ import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.FuseUnavailableMountException;
@@ -453,6 +460,9 @@ class StorageManagerService extends IStorageManager.Stub
private UserManagerInternal mUmInternal;
private ActivityManagerInternal mAmInternal;
+ private IPackageManager mIPackageManager;
+ private IAppOpsService mIAppOpsService;
+
private final Callbacks mCallbacks;
private final LockPatternUtils mLockPatternUtils;
@@ -1570,6 +1580,10 @@ class StorageManagerService extends IStorageManager.Stub
.registerScreenObserver(this);
mSystemReady = true;
+ mIPackageManager = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ mIAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
}
@@ -3117,7 +3131,8 @@ class StorageManagerService extends IStorageManager.Stub
throw new SecurityException("Shady looking path " + path);
}
- if (!mAmInternal.isAppStorageSandboxed(pid, uid)) {
+ final int mountMode = mAmInternal.getStorageMountMode(pid, uid);
+ if (mountMode == Zygote.MOUNT_EXTERNAL_FULL) {
return path;
}
@@ -3126,6 +3141,11 @@ class StorageManagerService extends IStorageManager.Stub
final String device = m.group(1);
final String devicePath = m.group(2);
+ if (mountMode == Zygote.MOUNT_EXTERNAL_INSTALLER
+ && devicePath.startsWith("Android/obb/")) {
+ return path;
+ }
+
// Does path belong to any packages belonging to this UID? If so,
// they get to go straight through to legacy paths.
final String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
@@ -3477,6 +3497,27 @@ class StorageManagerService extends IStorageManager.Stub
}
}
+ private int getMountMode(int uid, String packageName) {
+ try {
+ if (Process.isIsolated(uid)) {
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+ if (mIPackageManager.checkUidPermission(WRITE_MEDIA_STORAGE, uid)
+ == PERMISSION_GRANTED) {
+ return Zygote.MOUNT_EXTERNAL_FULL;
+ } else if (mIPackageManager.checkUidPermission(INSTALL_PACKAGES, uid)
+ == PERMISSION_GRANTED || mIAppOpsService.checkOperation(
+ OP_REQUEST_INSTALL_PACKAGES, uid, packageName) == MODE_ALLOWED) {
+ return Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else {
+ return Zygote.MOUNT_EXTERNAL_WRITE;
+ }
+ } catch (RemoteException e) {
+ // Should not happen
+ }
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_STORAGE_STATE_CHANGED = 1;
private static final int MSG_VOLUME_STATE_CHANGED = 2;
@@ -3718,6 +3759,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public int getExternalStorageMountMode(int uid, String packageName) {
+ if (ENABLE_ISOLATED_STORAGE) {
+ return getMountMode(uid, packageName);
+ }
// No locking - CopyOnWriteArrayList
int mountMode = Integer.MAX_VALUE;
for (ExternalStorageMountPolicy policy : mPolicies) {
@@ -3754,6 +3798,9 @@ class StorageManagerService extends IStorageManager.Stub
if (uid == Process.SYSTEM_UID) {
return true;
}
+ if (ENABLE_ISOLATED_STORAGE) {
+ return getMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE;
+ }
// No locking - CopyOnWriteArrayList
for (ExternalStorageMountPolicy policy : mPolicies) {
final boolean policyHasStorage = policy.hasExternalStorage(uid, packageName);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index bbb1d13bdcdc..9f353a80a5be 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -16,7 +16,9 @@
package com.android.server;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.IUidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -111,6 +113,7 @@ public class VibratorService extends IVibratorService.Stub
private final boolean mSupportsAmplitudeControl;
private final int mDefaultVibrationAmplitude;
private final SparseArray<VibrationEffect> mFallbackEffects;
+ private final SparseArray<Integer> mProcStatesCache = new SparseArray();
private final WorkSource mTmpWorkSource = new WorkSource();
private final Handler mH = new Handler();
private final Object mLock = new Object();
@@ -147,6 +150,25 @@ public class VibratorService extends IVibratorService.Stub
native static void vibratorSetAmplitude(int amplitude);
native static long vibratorPerformEffect(long effect, long strength);
+ private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+ mProcStatesCache.put(uid, procState);
+ }
+
+ @Override public void onUidGone(int uid, boolean disabled) {
+ mProcStatesCache.delete(uid);
+ }
+
+ @Override public void onUidActive(int uid) {
+ }
+
+ @Override public void onUidIdle(int uid, boolean disabled) {
+ }
+
+ @Override public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
+
private class Vibration implements IBinder.DeathRecipient {
public final IBinder token;
// Start time in CLOCK_BOOTTIME base.
@@ -411,6 +433,14 @@ public class VibratorService extends IVibratorService.Stub
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
updateVibrators();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -502,6 +532,12 @@ public class VibratorService extends IVibratorService.Stub
return;
}
verifyIncomingUid(uid);
+ if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
+ + uid + " is background");
+ return;
+ }
if (!verifyVibrationEffect(effect)) {
return;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f7acf7e83200..8751d24bcf67 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2019,6 +2019,13 @@ public final class ActiveServices {
ComponentName className = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
ComponentName name = comp != null ? comp : className;
+ if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid,
+ name.getPackageName(), sInfo.applicationInfo.uid)) {
+ String msg = "association not allowed between packages "
+ + callingPackage + " and " + r.packageName;
+ Slog.w(TAG, "Service lookup failed: " + msg);
+ return new ServiceLookupResult(null, msg);
+ }
if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
if (isBindExternal) {
if (!sInfo.exported) {
@@ -2099,6 +2106,17 @@ public final class ActiveServices {
}
}
if (r != null) {
+ if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, r.packageName,
+ r.appInfo.uid)) {
+ String msg = "association not allowed between packages "
+ + callingPackage + " and " + r.packageName;
+ Slog.w(TAG, "Service lookup failed: " + msg);
+ return new ServiceLookupResult(null, msg);
+ }
+ if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid,
+ resolvedType, r.appInfo)) {
+ return new ServiceLookupResult(null, "blocked by firewall");
+ }
if (mAm.checkComponentPermission(r.permission,
callingPid, callingUid, r.appInfo.uid, r.exported) != PERMISSION_GRANTED) {
if (!r.exported) {
@@ -2125,11 +2143,6 @@ public final class ActiveServices {
return null;
}
}
-
- if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid,
- resolvedType, r.appInfo)) {
- return null;
- }
return new ServiceLookupResult(r, null);
}
return null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e9db88a25c3..6700a530edc0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -658,6 +658,12 @@ public class ActivityManagerService extends IActivityManager.Stub
ArraySet<String> mBackgroundLaunchBroadcasts;
/**
+ * When an app has restrictions on the other apps that can have associations with it,
+ * it appears here with a set of the allowed apps.
+ */
+ ArrayMap<String, ArraySet<String>> mAllowedAssociations;
+
+ /**
* All of the processes we currently have running organized by pid.
* The keys are the pid running the application.
*
@@ -2396,6 +2402,34 @@ public class ActivityManagerService extends IActivityManager.Stub
return mBackgroundLaunchBroadcasts;
}
+ boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
+ if (mAllowedAssociations == null) {
+ mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations();
+ }
+ // Interactions with the system uid are always allowed, since that is the core system
+ // that everyone needs to be able to interact with.
+ if (UserHandle.getAppId(uid1) == SYSTEM_UID) {
+ return true;
+ }
+ if (UserHandle.getAppId(uid2) == SYSTEM_UID) {
+ return true;
+ }
+ // We won't allow this association if either pkg1 or pkg2 has a limit on the
+ // associations that are allowed with it, and the other package is not explicitly
+ // specified as one of those associations.
+ ArraySet<String> pkgs = mAllowedAssociations.get(pkg1);
+ if (pkgs != null) {
+ if (!pkgs.contains(pkg2)) {
+ return false;
+ }
+ }
+ pkgs = mAllowedAssociations.get(pkg2);
+ if (pkgs != null) {
+ return pkgs.contains(pkg1);
+ }
+ return true;
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -2724,47 +2758,86 @@ public class ActivityManagerService extends IActivityManager.Stub
return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0;
}
- void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) {
+ /**
+ * Update battery stats on the activity' usage.
+ * @param activity
+ * @param uid
+ * @param userId
+ * @param resumed
+ */
+ void updateBatteryStats(ComponentName activity, int uid, int userId, boolean resumed) {
if (DEBUG_SWITCH) {
Slog.d(TAG_SWITCH,
- "updateUsageStats: comp=" + activity + "res=" + resumed);
+ "updateBatteryStats: comp=" + activity + "res=" + resumed);
}
final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED,
- uid, activity.getPackageName(),
- activity.getShortClassName(), resumed ?
- StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND :
+ uid, activity.getPackageName(), activity.getShortClassName(),
+ resumed ? StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND :
StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__BACKGROUND);
- if (resumed) {
- if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(activity, userId,
- UsageEvents.Event.MOVE_TO_FOREGROUND);
-
- }
- synchronized (stats) {
+ synchronized (stats) {
+ if (resumed) {
stats.noteActivityResumedLocked(uid);
+ } else {
+ stats.noteActivityPausedLocked(uid);
}
- } else {
+ }
+ }
+
+ /**
+ * Update UsageStas on the activity's usage.
+ * @param activity
+ * @param userId
+ * @param event
+ * @param appToken ActivityRecord's appToken.
+ */
+ public void updateActivityUsageStats(ComponentName activity, int userId, int event,
+ IBinder appToken) {
+ if (DEBUG_SWITCH) {
+ Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
+ + activity + " hash=" + appToken.hashCode() + " event=" + event);
+ }
+ synchronized (this) {
if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(activity, userId,
- UsageEvents.Event.MOVE_TO_BACKGROUND);
+ mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
}
- synchronized (stats) {
- stats.noteActivityPausedLocked(uid);
+ }
+ }
+
+ /**
+ * Update UsageStats on this package's usage.
+ * @param packageName
+ * @param userId
+ * @param event
+ */
+ public void updateActivityUsageStats(String packageName, int userId, int event) {
+ if (DEBUG_SWITCH) {
+ Slog.d(TAG_SWITCH, "updateActivityUsageStats: package="
+ + packageName + " event=" + event);
+ }
+ synchronized (this) {
+ if (mUsageStatsService != null) {
+ mUsageStatsService.reportEvent(packageName, userId, event);
}
}
}
+ /**
+ * Update Usages on this foreground service's usage.
+ * @param service
+ * @param userId
+ * @param started
+ */
void updateForegroundServiceUsageStats(ComponentName service, int userId, boolean started) {
if (DEBUG_SWITCH) {
Slog.d(TAG_SWITCH, "updateForegroundServiceUsageStats: comp="
- + service + "started=" + started);
+ + service + " started=" + started);
}
synchronized (this) {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(service, userId,
started ? UsageEvents.Event.FOREGROUND_SERVICE_START
- : UsageEvents.Event.FOREGROUND_SERVICE_STOP);
+ : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
}
}
}
@@ -6225,6 +6298,21 @@ public class ActivityManagerService extends IActivityManager.Stub
return state != 'Z' && state != 'X' && state != 'x' && state != 'K';
}
+ private String checkContentProviderAssociation(ProcessRecord callingApp, int callingUid,
+ ProviderInfo cpi) {
+ if (callingApp == null) {
+ return validateAssociationAllowedLocked(cpi.packageName, cpi.applicationInfo.uid,
+ null, callingUid) ? null : "<null>";
+ }
+ for (int i = callingApp.pkgList.size() - 1; i >= 0; i--) {
+ if (!validateAssociationAllowedLocked(callingApp.pkgList.keyAt(i), callingApp.uid,
+ cpi.packageName, cpi.applicationInfo.uid)) {
+ return cpi.packageName;
+ }
+ }
+ return null;
+ }
+
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, int callingUid, String callingTag, boolean stable,
int userId) {
@@ -6296,6 +6384,11 @@ public class ActivityManagerService extends IActivityManager.Stub
String msg;
if (r != null && cpr.canRunHere(r)) {
+ if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
+ throw new SecurityException("Content provider lookup "
+ + cpr.name.flattenToShortString()
+ + " failed: association not allowed with package " + msg);
+ }
checkTime(startTime,
"getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
@@ -6325,6 +6418,11 @@ public class ActivityManagerService extends IActivityManager.Stub
} catch (RemoteException e) {
}
+ if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
+ throw new SecurityException("Content provider lookup "
+ + cpr.name.flattenToShortString()
+ + " failed: association not allowed with package " + msg);
+ }
checkTime(startTime,
"getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
@@ -6422,6 +6520,11 @@ public class ActivityManagerService extends IActivityManager.Stub
checkTime(startTime, "getContentProviderImpl: got app info for user");
String msg;
+ if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
+ throw new SecurityException("Content provider lookup "
+ + cpr.name.flattenToShortString()
+ + " failed: association not allowed with package " + msg);
+ }
checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
!= null) {
@@ -9168,6 +9271,12 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println("-------------------------------------------------------------------------------");
}
+ dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+
+ }
mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
@@ -9435,6 +9544,14 @@ public class ActivityManagerService extends IActivityManager.Stub
System.gc();
pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
}
+ } else if ("allowed-associations".equals(cmd)) {
+ if (opti < args.length) {
+ dumpPackage = args[opti];
+ opti++;
+ }
+ synchronized (this) {
+ dumpAllowedAssociationsLocked(fd, pw, args, opti, true, dumpPackage);
+ }
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
if (opti < args.length) {
dumpPackage = args[opti];
@@ -10785,6 +10902,44 @@ public class ActivityManagerService extends IActivityManager.Stub
proto.end(handlerToken);
}
+ void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ int opti, boolean dumpAll, String dumpPackage) {
+ boolean needSep = false;
+ boolean printedAnything = false;
+
+ pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
+ boolean printed = false;
+ if (mAllowedAssociations != null) {
+ for (int i = 0; i < mAllowedAssociations.size(); i++) {
+ final String pkg = mAllowedAssociations.keyAt(i);
+ final ArraySet<String> asc = mAllowedAssociations.valueAt(i);
+ boolean printedHeader = false;
+ for (int j = 0; j < asc.size(); j++) {
+ if (dumpPackage == null || pkg.equals(dumpPackage)
+ || asc.valueAt(j).equals(dumpPackage)) {
+ if (!printed) {
+ pw.println(" Allowed associations (by restricted package):");
+ printed = true;
+ needSep = true;
+ printedAnything = true;
+ }
+ if (!printedHeader) {
+ pw.print(" * ");
+ pw.print(pkg);
+ pw.println(":");
+ printedHeader = true;
+ }
+ pw.print(" Allow: ");
+ pw.println(asc.valueAt(j));
+ }
+ }
+ }
+ }
+ if (!printed) {
+ pw.println(" (No association restrictions)");
+ }
+ }
+
void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
@@ -19068,9 +19223,19 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) {
+ public void updateBatteryStats(ComponentName activity, int uid, int userId,
+ boolean resumed) {
synchronized (ActivityManagerService.this) {
- ActivityManagerService.this.updateUsageStats(activity, uid, userId, resumed);
+ ActivityManagerService.this.updateBatteryStats(activity, uid, userId, resumed);
+ }
+ }
+
+ @Override
+ public void updateActivityUsageStats(ComponentName activity, int userId, int event,
+ IBinder appToken) {
+ synchronized (ActivityManagerService.this) {
+ ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
+ appToken);
}
}
@@ -19392,16 +19557,13 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public boolean isAppStorageSandboxed(int pid, int uid) {
- if (!StorageManager.hasIsolatedStorage()) {
- return false;
- }
+ public int getStorageMountMode(int pid, int uid) {
if (uid == SHELL_UID || uid == ROOT_UID) {
- return false;
+ return Zygote.MOUNT_EXTERNAL_FULL;
}
synchronized (mPidsSelfLocked) {
final ProcessRecord pr = mPidsSelfLocked.get(pid);
- return pr == null || pr.mountMode != Zygote.MOUNT_EXTERNAL_FULL;
+ return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.mountMode;
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 67a4d14a6edb..98a82acd79a6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2852,6 +2852,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" prov[iders] [COMP_SPEC ...]: content provider state");
pw.println(" provider [COMP_SPEC]: provider client-side state");
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
+ pw.println(" allowed-associations: current package association restrictions");
pw.println(" as[sociations]: tracked app associations");
pw.println(" lmk: stats on low memory killer");
pw.println(" lru: raw LRU process list");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 3a0899de75c3..c290fbe09864 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -527,6 +527,24 @@ public final class BroadcastQueue {
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered, int index) {
boolean skip = false;
+ if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+ filter.packageName, filter.owningUid)) {
+ Slog.w(TAG, "Association not allowed: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+ + filter);
+ skip = true;
+ }
+ if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+ r.callingPid, r.resolvedType, filter.receiverList.uid)) {
+ Slog.w(TAG, "Firewall blocked: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+ + filter);
+ skip = true;
+ }
if (filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
@@ -619,11 +637,6 @@ public final class BroadcastQueue {
skip = true;
}
- if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
- r.callingPid, r.resolvedType, filter.receiverList.uid)) {
- skip = true;
- }
-
if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed
|| filter.receiverList.app.isCrashing())) {
Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
@@ -1082,6 +1095,24 @@ public final class BroadcastQueue {
> brOptions.getMaxManifestReceiverApiLevel())) {
skip = true;
}
+ if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+ component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
+ Slog.w(TAG, "Association not allowed: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+ skip = true;
+ }
+ if (!skip) {
+ skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+ r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
+ if (skip) {
+ Slog.w(TAG, "Firewall blocked: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+ }
+ }
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
@@ -1170,10 +1201,6 @@ public final class BroadcastQueue {
+ " (uid " + r.callingUid + ")");
skip = true;
}
- if (!skip) {
- skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
- r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
- }
boolean isSingleton = false;
try {
isSingleton = mService.isSingleton(info.activityInfo.processName,
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 9649ccd3c750..32219aa5955f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -282,9 +282,11 @@ public abstract class BiometricServiceBase extends SystemService
public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int userId, int groupId,
- byte[] cryptoToken, boolean restricted, String owner) {
+ byte[] cryptoToken, boolean restricted, String owner,
+ final int[] disabledFeatures) {
super(context, getMetrics(), daemon, halDeviceId, token, listener,
- userId, groupId, cryptoToken, restricted, owner, getBiometricUtils());
+ userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(),
+ disabledFeatures);
}
@Override
@@ -408,7 +410,8 @@ public abstract class BiometricServiceBase extends SystemService
int cancel() throws RemoteException;
int remove(int groupId, int biometricId) throws RemoteException;
int enumerate() throws RemoteException;
- int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException;
+ int enroll(byte[] cryptoToken, int groupId, int timeout,
+ ArrayList<Integer> disabledFeatures) throws RemoteException;
}
/**
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index f858ef5ec6f8..8a0f0858cedc 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -34,15 +34,18 @@ public abstract class EnrollClient extends ClientMonitor {
private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
private final byte[] mCryptoToken;
private final BiometricUtils mBiometricUtils;
+ private final int[] mDisabledFeatures;
public EnrollClient(Context context, Metrics metrics,
BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
BiometricServiceBase.ServiceListener listener, int userId, int groupId,
- byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) {
+ byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils,
+ final int[] disabledFeatures) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
owner, 0 /* cookie */);
mBiometricUtils = utils;
mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
+ mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
}
@Override
@@ -74,7 +77,13 @@ public abstract class EnrollClient extends ClientMonitor {
public int start() {
final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
try {
- final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout);
+ final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+ for (int i = 0; i < mDisabledFeatures.length; i++) {
+ disabledFeatures.add(mDisabledFeatures[i]);
+ }
+
+ final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout,
+ disabledFeatures);
if (result != 0) {
Slog.w(getLogTag(), "startEnroll failed, result=" + result);
mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result);
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 557af0478b87..72f73f6aaf67 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -113,17 +113,17 @@ public class FaceService extends BiometricServiceBase {
}
@Override // Binder call
- public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
- final IFaceServiceReceiver receiver, final int flags,
- final String opPackageName) {
+ public void enroll(final IBinder token, final byte[] cryptoToken,
+ final IFaceServiceReceiver receiver, final String opPackageName,
+ final int[] disabledFeatures) {
checkPermission(MANAGE_BIOMETRIC);
final boolean restricted = isRestricted();
final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
- 0 /* groupId */, cryptoToken, restricted, opPackageName);
+ 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures);
- enrollInternal(client, userId);
+ enrollInternal(client, UserHandle.getCallingUserId());
}
@Override // Binder call
@@ -333,7 +333,7 @@ public class FaceService extends BiometricServiceBase {
}
@Override
- public int setRequireAttention(boolean requireAttention, final byte[] token) {
+ public int setFeature(int feature, boolean enabled, final byte[] token) {
checkPermission(MANAGE_BIOMETRIC);
final ArrayList<Byte> byteToken = new ArrayList<>();
@@ -343,10 +343,11 @@ public class FaceService extends BiometricServiceBase {
int result;
try {
- result = mDaemon != null ? mDaemon.setRequireAttention(requireAttention, byteToken)
+ result = mDaemon != null ? mDaemon.setFeature(feature, enabled, byteToken)
: Status.INTERNAL_ERROR;
} catch (RemoteException e) {
- Slog.e(getTag(), "Unable to setRequireAttention to " + requireAttention, e);
+ Slog.e(getTag(), "Unable to set feature: " + feature + " to enabled:" + enabled,
+ e);
result = Status.INTERNAL_ERROR;
}
@@ -354,17 +355,12 @@ public class FaceService extends BiometricServiceBase {
}
@Override
- public boolean getRequireAttention(final byte[] token) {
+ public boolean getFeature(int feature) {
checkPermission(MANAGE_BIOMETRIC);
- final ArrayList<Byte> byteToken = new ArrayList<>();
- for (int i = 0; i < token.length; i++) {
- byteToken.add(token[i]);
- }
-
boolean result = true;
try {
- result = mDaemon != null ? mDaemon.getRequireAttention(byteToken).value : true;
+ result = mDaemon != null ? mDaemon.getFeature(feature) : true;
} catch (RemoteException e) {
Slog.e(getTag(), "Unable to getRequireAttention", e);
}
@@ -612,7 +608,8 @@ public class FaceService extends BiometricServiceBase {
}
@Override
- public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException {
+ public int enroll(byte[] cryptoToken, int groupId, int timeout,
+ ArrayList<Integer> disabledFeatures) throws RemoteException {
IBiometricsFace daemon = getFaceDaemon();
if (daemon == null) {
Slog.w(TAG, "enroll(): no face HAL!");
@@ -623,7 +620,7 @@ public class FaceService extends BiometricServiceBase {
token.add(cryptoToken[i]);
}
// TODO: plumb requireAttention down from framework
- return daemon.enroll(token, timeout, true /* requireAttention */);
+ return daemon.enroll(token, timeout, disabledFeatures);
}
};
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 6a5bc61f2eb6..3895ef78b357 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -144,7 +144,7 @@ public class FingerprintService extends BiometricServiceBase {
final int groupId = userId; // default group for fingerprint enrollment
final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
- cryptoToken, restricted, opPackageName);
+ cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */);
enrollInternal(client, userId);
}
@@ -716,7 +716,8 @@ public class FingerprintService extends BiometricServiceBase {
}
@Override
- public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException {
+ public int enroll(byte[] cryptoToken, int groupId, int timeout,
+ ArrayList<Integer> disabledFeatures) throws RemoteException {
IBiometricsFingerprint daemon = getFingerprintDaemon();
if (daemon == null) {
Slog.w(TAG, "enroll(): no fingerprint HAL!");
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 360a7d105cce..cf8d21b66417 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -46,6 +46,8 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
import android.hardware.display.DisplayViewport;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
import android.hardware.display.IDisplayManager;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
@@ -1241,6 +1243,29 @@ public final class DisplayManagerService extends SystemService {
}
}
+ @VisibleForTesting
+ DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributesInternal(
+ int displayId) {
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId);
+ return SurfaceControl.getDisplayedContentSamplingAttributes(displayToken);
+ }
+
+ @VisibleForTesting
+ boolean setDisplayedContentSamplingEnabledInternal(
+ int displayId, boolean enable, int componentMask, int maxFrames) {
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId);
+ return SurfaceControl.setDisplayedContentSamplingEnabled(
+ displayToken, enable, componentMask, maxFrames);
+ }
+
+ @VisibleForTesting
+ DisplayedContentSample getDisplayedContentSampleInternal(int displayId,
+ long maxFrames, long timestamp) {
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId);
+ return SurfaceControl.getDisplayedContentSample(
+ displayToken, maxFrames, timestamp);
+ }
+
private void clearViewportsLocked() {
mViewports.clear();
}
@@ -2331,5 +2356,25 @@ public final class DisplayManagerService extends SystemService {
}
}
}
+
+ @Override
+ public DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+ int displayId) {
+ return getDisplayedContentSamplingAttributesInternal(displayId);
+ }
+
+ @Override
+ public boolean setDisplayedContentSamplingEnabled(
+ int displayId, boolean enable, int componentMask, int maxFrames) {
+ return setDisplayedContentSamplingEnabledInternal(
+ displayId, enable, componentMask, maxFrames);
+ }
+
+ @Override
+ public DisplayedContentSample getDisplayedContentSample(int displayId,
+ long maxFrames, long timestamp) {
+ return getDisplayedContentSampleInternal(displayId, maxFrames, timestamp);
+ }
+
}
}
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 98e32997e587..0d64dbd83a34 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -1,4 +1,5 @@
michaelwr@google.com
+dangittik@google.com
hackbod@google.com
ogunwale@google.com
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 611c8b728d0c..78e18e91ae73 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -839,6 +839,15 @@ public class JobSchedulerService extends com.android.server.SystemService
break;
}
}
+ if (DEBUG) {
+ Slog.d(TAG, "Something in " + pkgName
+ + " changed. Reevaluating controller states.");
+ }
+ synchronized (mLock) {
+ for (int c = mControllers.size() - 1; c >= 0; --c) {
+ mControllers.get(c).reevaluateStateLocked(pkgUid);
+ }
+ }
}
} else {
Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
@@ -1042,6 +1051,8 @@ public class JobSchedulerService extends com.android.server.SystemService
mJobPackageTracker.notePending(jobStatus);
addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
maybeRunPendingJobsLocked();
+ } else {
+ evaluateControllerStatesLocked(jobStatus);
}
}
return JobScheduler.RESULT_SUCCESS;
@@ -1884,6 +1895,8 @@ public class JobSchedulerService extends com.android.server.SystemService
newReadyJobs = new ArrayList<JobStatus>();
}
newReadyJobs.add(job);
+ } else {
+ evaluateControllerStatesLocked(job);
}
}
@@ -1957,6 +1970,8 @@ public class JobSchedulerService extends com.android.server.SystemService
runnableJobs = new ArrayList<>();
}
runnableJobs.add(job);
+ } else {
+ evaluateControllerStatesLocked(job);
}
}
@@ -2087,6 +2102,15 @@ public class JobSchedulerService extends com.android.server.SystemService
HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
}
+ /** Returns true if both the calling and source users for the job are started. */
+ private boolean areUsersStartedLocked(final JobStatus job) {
+ boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
+ if (job.getUserId() == job.getSourceUserId()) {
+ return sourceStarted;
+ }
+ return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId());
+ }
+
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
@@ -2219,6 +2243,61 @@ public class JobSchedulerService extends com.android.server.SystemService
return componentPresent;
}
+ private void evaluateControllerStatesLocked(final JobStatus job) {
+ for (int c = mControllers.size() - 1; c >= 0; --c) {
+ final StateController sc = mControllers.get(c);
+ sc.evaluateStateLocked(job);
+ }
+ }
+
+ /**
+ * Returns true if non-job constraint components are in place -- if job.isReady() returns true
+ * and this method returns true, then the job is ready to be executed.
+ */
+ public boolean areComponentsInPlaceLocked(JobStatus job) {
+ // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same
+ // conditions.
+
+ final boolean jobExists = mJobs.containsJob(job);
+ final boolean userStarted = areUsersStartedLocked(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
+ + " exists=" + jobExists + " userStarted=" + userStarted);
+ }
+
+ // These are also fairly cheap to check, though they typically will not
+ // be conditions we fail.
+ if (!jobExists || !userStarted) {
+ return false;
+ }
+
+ // Job pending/active doesn't affect the readiness of a job.
+
+ // Skipping the hearbeat check as this will only come into play when using the rolling
+ // window quota management system.
+
+ // The expensive check last: validate that the defined package+service is
+ // still present & viable.
+ final boolean componentPresent;
+ try {
+ // TODO: cache result until we're notified that something in the package changed.
+ componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
+ job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ job.getUserId()) != null);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
+ + " componentPresent=" + componentPresent);
+ }
+
+ // Everything else checked out so far, so this is the final yes/no check
+ return componentPresent;
+ }
+
/**
* Reconcile jobs in the pending queue against available execution contexts.
* A controller can force a job into the pending queue even if it's already running, but
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 6989c334d876..8f104e4a1525 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -41,10 +41,12 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.JobServiceContext;
import com.android.server.job.StateControllerProto;
+import com.android.server.net.NetworkPolicyManagerInternal;
import java.util.Objects;
import java.util.function.Predicate;
@@ -66,16 +68,29 @@ public final class ConnectivityController extends StateController implements
private final ConnectivityManager mConnManager;
private final NetworkPolicyManager mNetPolicyManager;
+ private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
/** List of tracked jobs keyed by source UID. */
@GuardedBy("mLock")
private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
+ /**
+ * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
+ * grant an exception to in the app standby chain.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();
+
+ /** List of currently available networks. */
+ @GuardedBy("mLock")
+ private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();
+
public ConnectivityController(JobSchedulerService service) {
super(service);
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
+ mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
// We're interested in all network changes; internally we match these
// network changes against the active network for each UID with jobs.
@@ -109,7 +124,176 @@ public final class ConnectivityController extends StateController implements
if (jobs != null) {
jobs.remove(jobStatus);
}
+ maybeRevokeStandbyExceptionLocked(jobStatus);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ public void onConstantsUpdatedLocked() {
+ if (mConstants.USE_HEARTBEATS) {
+ // App idle exceptions are only requested for the rolling quota system.
+ if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions");
+ for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) {
+ int uid = mRequestedWhitelistJobs.keyAt(i);
+ mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
+ }
+ mRequestedWhitelistJobs.clear();
+ }
+ }
+
+ /**
+ * Returns true if the job's requested network is available. This DOES NOT necesarilly mean
+ * that the UID has been granted access to the network.
+ */
+ public boolean isNetworkAvailable(JobStatus job) {
+ synchronized (mLock) {
+ for (int i = 0; i < mAvailableNetworks.size(); ++i) {
+ final Network network = mAvailableNetworks.valueAt(i);
+ final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(
+ network);
+ final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
+ if (DEBUG) {
+ Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
+ + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
+ }
+ if (satisfied) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
+ * chain.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void requestStandbyExceptionLocked(JobStatus job) {
+ final int uid = job.getSourceUid();
+ // Need to call this before adding the job.
+ final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
+ ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
+ if (jobs == null) {
+ jobs = new ArraySet<JobStatus>();
+ mRequestedWhitelistJobs.put(uid, jobs);
+ }
+ if (!jobs.add(job) || isExceptionRequested) {
+ if (DEBUG) {
+ Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
+ }
+ return;
}
+ if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
+ mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
+ }
+
+ /** Returns whether a standby exception has been requested for the UID. */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ boolean isStandbyExceptionRequestedLocked(final int uid) {
+ ArraySet jobs = mRequestedWhitelistJobs.get(uid);
+ return jobs != null && jobs.size() > 0;
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) {
+ final boolean networkAvailable = isNetworkAvailable(jobStatus);
+ if (DEBUG) {
+ Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString()
+ + " networkAvailable=" + networkAvailable);
+ }
+ // If the network isn't available, then requesting an exception won't help.
+
+ return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus,
+ JobStatus.CONSTRAINT_CONNECTIVITY);
+ }
+
+ /**
+ * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
+ * thing stopping a job from running.
+ */
+ @GuardedBy("mLock")
+ @Override
+ public void evaluateStateLocked(JobStatus jobStatus) {
+ if (mConstants.USE_HEARTBEATS) {
+ // This should only be used for the rolling quota system.
+ return;
+ }
+
+ if (!jobStatus.hasConnectivityConstraint()) {
+ return;
+ }
+
+ // Always check the full job readiness stat in case the component has been disabled.
+ if (wouldBeReadyWithConnectivityLocked(jobStatus)) {
+ if (DEBUG) {
+ Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
+ }
+ requestStandbyExceptionLocked(jobStatus);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
+ }
+ maybeRevokeStandbyExceptionLocked(jobStatus);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ public void reevaluateStateLocked(final int uid) {
+ if (mConstants.USE_HEARTBEATS) {
+ return;
+ }
+ // Check if we still need a connectivity exception in case the JobService was disabled.
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
+ if (jobs == null) {
+ return;
+ }
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ evaluateStateLocked(jobs.valueAt(i));
+ }
+ }
+
+ /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
+ final int uid = job.getSourceUid();
+ if (!isStandbyExceptionRequestedLocked(uid)) {
+ return;
+ }
+ ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
+ if (jobs == null) {
+ Slog.wtf(TAG,
+ "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
+ + "standby exception has been requested.");
+ return;
+ }
+ if (!jobs.remove(job) || jobs.size() > 0) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "maybeRevokeStandbyExceptionLocked not revoking because there are still "
+ + jobs.size() + " jobs left.");
+ }
+ return;
+ }
+ // No more jobs that need an exception.
+ revokeStandbyExceptionLocked(uid);
+ }
+
+ /**
+ * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
+ * for the uid.
+ */
+ @GuardedBy("mLock")
+ private void revokeStandbyExceptionLocked(final int uid) {
+ if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
+ mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
+ mRequestedWhitelistJobs.remove(uid);
}
/**
@@ -326,6 +510,14 @@ public final class ConnectivityController extends StateController implements
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
+ public void onAvailable(Network network) {
+ if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
+ synchronized (mLock) {
+ mAvailableNetworks.add(network);
+ }
+ }
+
+ @Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
if (DEBUG) {
Slog.v(TAG, "onCapabilitiesChanged: " + network);
@@ -338,6 +530,9 @@ public final class ConnectivityController extends StateController implements
if (DEBUG) {
Slog.v(TAG, "onLost: " + network);
}
+ synchronized (mLock) {
+ mAvailableNetworks.remove(network);
+ }
updateTrackedJobs(-1, network);
}
};
@@ -356,6 +551,27 @@ public final class ConnectivityController extends StateController implements
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate) {
+ if (mRequestedWhitelistJobs.size() > 0) {
+ pw.print("Requested standby exceptions:");
+ for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
+ pw.print(" ");
+ pw.print(mRequestedWhitelistJobs.keyAt(i));
+ pw.print(" (");
+ pw.print(mRequestedWhitelistJobs.valueAt(i).size());
+ pw.print(" jobs)");
+ }
+ pw.println();
+ }
+ if (mAvailableNetworks.size() > 0) {
+ pw.println("Available networks:");
+ pw.increaseIndent();
+ for (int i = 0; i < mAvailableNetworks.size(); i++) {
+ pw.println(mAvailableNetworks.valueAt(i));
+ }
+ pw.decreaseIndent();
+ } else {
+ pw.println("No available networks");
+ }
for (int i = 0; i < mTrackedJobs.size(); i++) {
final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
for (int j = 0; j < jobs.size(); j++) {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 434158957c17..82bfa511507f 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -1007,6 +1007,18 @@ public final class JobStatus {
* @return Whether or not this job is ready to run, based on its requirements.
*/
public boolean isReady() {
+ return isReady(mSatisfiedConstraintsOfInterest);
+ }
+
+ /**
+ * @return Whether or not this job would be ready to run if it had the specified constraint
+ * granted, based on its requirements.
+ */
+ public boolean wouldBeReadyWithConstraint(int constraint) {
+ return isReady(mSatisfiedConstraintsOfInterest | constraint);
+ }
+
+ private boolean isReady(int satisfiedConstraints) {
// Quota constraints trumps all other constraints.
if (!mReadyWithinQuota) {
return false;
@@ -1017,7 +1029,7 @@ public final class JobStatus {
// DeviceNotDozing implicit constraint must be satisfied
// NotRestrictedInBackground implicit constraint must be satisfied
return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
- || isConstraintsSatisfied());
+ || isConstraintsSatisfied(satisfiedConstraints));
}
static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
@@ -1033,12 +1045,16 @@ public final class JobStatus {
* @return Whether the constraints set on this job are satisfied.
*/
public boolean isConstraintsSatisfied() {
+ return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest);
+ }
+
+ private boolean isConstraintsSatisfied(int satisfiedConstraints) {
if (overrideState == OVERRIDE_FULL) {
// force override: the job is always runnable
return true;
}
- int sat = mSatisfiedConstraintsOfInterest;
+ int sat = satisfiedConstraints;
if (overrideState == OVERRIDE_SOFT) {
// override: pretend all 'soft' requirements are satisfied
sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index f73ffac96dfa..660c2383ea2f 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -151,8 +151,7 @@ public final class QuotaController extends StateController {
return "<" + userId + ">" + packageName;
}
- @VisibleForTesting
- static final class Package {
+ private static final class Package {
public final String packageName;
public final int userId;
@@ -387,8 +386,9 @@ public final class QuotaController extends StateController {
private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
- return isWithinQuotaLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
- standbyBucket);
+ // Jobs for the active app should always be able to run.
+ return jobStatus.uidActive || isWithinQuotaLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
}
private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
@@ -579,7 +579,10 @@ public final class QuotaController extends StateController {
boolean changed = false;
for (int i = jobs.size() - 1; i >= 0; --i) {
final JobStatus js = jobs.valueAt(i);
- if (realStandbyBucket == getEffectiveStandbyBucket(js)) {
+ if (js.uidActive) {
+ // Jobs for the active app should always be able to run.
+ changed |= js.setQuotaConstraintSatisfied(true);
+ } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) {
changed |= js.setQuotaConstraintSatisfied(realInQuota);
} else {
// This job is somehow exempted. Need to determine its own quota status.
@@ -765,18 +768,18 @@ public final class QuotaController extends StateController {
public final long startTimeElapsed;
// End timestamp in elapsed realtime timebase.
public final long endTimeElapsed;
- // How many jobs ran during this session.
- public final int jobCount;
+ // How many background jobs ran during this session.
+ public final int bgJobCount;
TimingSession(long startElapsed, long endElapsed, int jobCount) {
this.startTimeElapsed = startElapsed;
this.endTimeElapsed = endElapsed;
- this.jobCount = jobCount;
+ this.bgJobCount = jobCount;
}
@Override
public String toString() {
- return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + jobCount
+ return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
+ "}";
}
@@ -786,7 +789,7 @@ public final class QuotaController extends StateController {
TimingSession other = (TimingSession) obj;
return startTimeElapsed == other.startTimeElapsed
&& endTimeElapsed == other.endTimeElapsed
- && jobCount == other.jobCount;
+ && bgJobCount == other.bgJobCount;
} else {
return false;
}
@@ -794,7 +797,7 @@ public final class QuotaController extends StateController {
@Override
public int hashCode() {
- return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, jobCount});
+ return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount});
}
public void dump(IndentingPrintWriter pw) {
@@ -804,8 +807,8 @@ public final class QuotaController extends StateController {
pw.print(" (");
pw.print(endTimeElapsed - startTimeElapsed);
pw.print("), ");
- pw.print(jobCount);
- pw.print(" jobs.");
+ pw.print(bgJobCount);
+ pw.print(" bg jobs.");
pw.println();
}
@@ -816,7 +819,8 @@ public final class QuotaController extends StateController {
startTimeElapsed);
proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED,
endTimeElapsed);
- proto.write(StateControllerProto.QuotaController.TimingSession.JOB_COUNT, jobCount);
+ proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT,
+ bgJobCount);
proto.end(token);
}
@@ -825,23 +829,32 @@ public final class QuotaController extends StateController {
private final class Timer {
private final Package mPkg;
- // List of jobs currently running for this package.
- private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
+ // List of jobs currently running for this app that started when the app wasn't in the
+ // foreground.
+ private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
private long mStartTimeElapsed;
- private int mJobCount;
+ private int mBgJobCount;
Timer(int userId, String packageName) {
mPkg = new Package(userId, packageName);
}
void startTrackingJob(@NonNull JobStatus jobStatus) {
+ if (jobStatus.uidActive) {
+ // We intentionally don't pay attention to fg state changes after a job has started.
+ if (DEBUG) {
+ Slog.v(TAG,
+ "Timer ignoring " + jobStatus.toShortString() + " because uidActive");
+ }
+ return;
+ }
if (DEBUG) Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
synchronized (mLock) {
// Always track jobs, even when charging.
- mRunningJobs.add(jobStatus);
+ mRunningBgJobs.add(jobStatus);
if (!mChargeTracker.isCharging()) {
- mJobCount++;
- if (mRunningJobs.size() == 1) {
+ mBgJobCount++;
+ if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
mStartTimeElapsed = sElapsedRealtimeClock.millis();
scheduleCutoff();
@@ -853,7 +866,7 @@ public final class QuotaController extends StateController {
void stopTrackingJob(@NonNull JobStatus jobStatus) {
if (DEBUG) Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
synchronized (mLock) {
- if (mRunningJobs.size() == 0) {
+ if (mRunningBgJobs.size() == 0) {
// maybeStopTrackingJobLocked can be called when an app cancels a job, so a
// timer may not be running when it's asked to stop tracking a job.
if (DEBUG) {
@@ -861,8 +874,8 @@ public final class QuotaController extends StateController {
}
return;
}
- mRunningJobs.remove(jobStatus);
- if (!mChargeTracker.isCharging() && mRunningJobs.size() == 0) {
+ if (mRunningBgJobs.remove(jobStatus)
+ && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) {
emitSessionLocked(sElapsedRealtimeClock.millis());
cancelCutoff();
}
@@ -870,13 +883,13 @@ public final class QuotaController extends StateController {
}
private void emitSessionLocked(long nowElapsed) {
- if (mJobCount <= 0) {
+ if (mBgJobCount <= 0) {
// Nothing to emit.
return;
}
- TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mJobCount);
+ TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
saveTimingSession(mPkg.userId, mPkg.packageName, ts);
- mJobCount = 0;
+ mBgJobCount = 0;
// Don't reset the tracked jobs list as we need to keep tracking the current number
// of jobs.
// However, cancel the currently scheduled cutoff since it's not currently useful.
@@ -889,7 +902,7 @@ public final class QuotaController extends StateController {
*/
public boolean isActive() {
synchronized (mLock) {
- return mJobCount > 0;
+ return mBgJobCount > 0;
}
}
@@ -905,12 +918,12 @@ public final class QuotaController extends StateController {
emitSessionLocked(nowElapsed);
} else {
// Start timing from unplug.
- if (mRunningJobs.size() > 0) {
+ if (mRunningBgJobs.size() > 0) {
mStartTimeElapsed = nowElapsed;
// NOTE: this does have the unfortunate consequence that if the device is
// repeatedly plugged in and unplugged, the job count for a package may be
// artificially high.
- mJobCount = mRunningJobs.size();
+ mBgJobCount = mRunningBgJobs.size();
// Schedule cutoff since we're now actively tracking for quotas again.
scheduleCutoff();
}
@@ -958,12 +971,12 @@ public final class QuotaController extends StateController {
pw.print("NOT active");
}
pw.print(", ");
- pw.print(mJobCount);
- pw.print(" running jobs");
+ pw.print(mBgJobCount);
+ pw.print(" running bg jobs");
pw.println();
pw.increaseIndent();
- for (int i = 0; i < mRunningJobs.size(); i++) {
- JobStatus js = mRunningJobs.valueAt(i);
+ for (int i = 0; i < mRunningBgJobs.size(); i++) {
+ JobStatus js = mRunningBgJobs.valueAt(i);
if (predicate.test(js)) {
pw.println(js.toShortString());
}
@@ -979,9 +992,9 @@ public final class QuotaController extends StateController {
proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
mStartTimeElapsed);
- proto.write(StateControllerProto.QuotaController.Timer.JOB_COUNT, mJobCount);
- for (int i = 0; i < mRunningJobs.size(); i++) {
- JobStatus js = mRunningJobs.valueAt(i);
+ proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount);
+ for (int i = 0; i < mRunningBgJobs.size(); i++) {
+ JobStatus js = mRunningBgJobs.valueAt(i);
if (predicate.test(js)) {
js.writeToShortProto(proto,
StateControllerProto.QuotaController.Timer.RUNNING_JOBS);
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index b439c0ddd028..61dc4799f221 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -16,7 +16,10 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.DEBUG;
+
import android.content.Context;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.IndentingPrintWriter;
@@ -32,6 +35,8 @@ import java.util.function.Predicate;
* are ready to run, or whether they must be stopped.
*/
public abstract class StateController {
+ private static final String TAG = "JobScheduler.SC";
+
protected final JobSchedulerService mService;
protected final StateChangedListener mStateChangedListener;
protected final Context mContext;
@@ -78,6 +83,37 @@ public abstract class StateController {
public void onConstantsUpdatedLocked() {
}
+ protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
+ // This is very cheap to check (just a few conditions on data in JobStatus).
+ final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint);
+ if (DEBUG) {
+ Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString()
+ + " readyWithConstraint=" + jobWouldBeReady);
+ }
+ if (!jobWouldBeReady) {
+ // If the job wouldn't be ready, nothing to do here.
+ return false;
+ }
+
+ // This is potentially more expensive since JSS may have to query component
+ // presence.
+ return mService.areComponentsInPlaceLocked(jobStatus);
+ }
+
+ /**
+ * Called when JobSchedulerService has determined that the job is not ready to be run. The
+ * Controller can evaluate if it can or should do something to promote this job's readiness.
+ */
+ public void evaluateStateLocked(JobStatus jobStatus) {
+ }
+
+ /**
+ * Called when something with the UID has changed. The controller should re-evaluate any
+ * internal state tracking dependent on this UID.
+ */
+ public void reevaluateStateLocked(int uid) {
+ }
+
public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate);
public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS
index 7e7335d68d3b..c7c6d5658d1d 100644
--- a/services/core/java/com/android/server/lights/OWNERS
+++ b/services/core/java/com/android/server/lights/OWNERS
@@ -1 +1,2 @@
michaelwr@google.com
+dangittik@google.com
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
new file mode 100644
index 000000000000..2ae424dd4b1b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.server.pm.dex.DexLogger;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and
+ * charging. The actual logging is performed by {@link DexLogger}.
+ * {@hide}
+ */
+public class DynamicCodeLoggingService extends JobService {
+ private static final String TAG = DynamicCodeLoggingService.class.getName();
+
+ private static final int JOB_ID = 2030028;
+ private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+
+ private volatile boolean mStopRequested = false;
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Schedule our job with the {@link JobScheduler}.
+ */
+ public static void schedule(Context context) {
+ ComponentName serviceName = new ComponentName(
+ "android", DynamicCodeLoggingService.class.getName());
+
+ JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ js.schedule(new JobInfo.Builder(JOB_ID, serviceName)
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(PERIOD_MILLIS)
+ .build());
+ if (DEBUG) {
+ Log.d(TAG, "Job scheduled");
+ }
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (DEBUG) {
+ Log.d(TAG, "onStartJob");
+ }
+ mStopRequested = false;
+ new IdleLoggingThread(params).start();
+ return true; // Job is running on another thread
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (DEBUG) {
+ Log.d(TAG, "onStopJob");
+ }
+ mStopRequested = true;
+ return true; // Requests job be re-scheduled.
+ }
+
+ private class IdleLoggingThread extends Thread {
+ private final JobParameters mParams;
+
+ IdleLoggingThread(JobParameters params) {
+ super("DynamicCodeLoggingService_IdleLoggingJob");
+ mParams = params;
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "Starting logging run");
+ }
+
+ PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
+ DexLogger dexLogger = pm.getDexManager().getDexLogger();
+ for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
+ if (mStopRequested) {
+ Log.w(TAG, "Stopping logging run at scheduler request");
+ return;
+ }
+
+ dexLogger.logDynamicCodeLoading(packageName);
+ }
+
+ jobFinished(mParams, /* reschedule */ false);
+ if (DEBUG) {
+ Log.d(TAG, "Finished logging run");
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 57922d052984..a95e73069568 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -27,7 +27,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
import android.app.PackageInstallObserver;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.Intent;
@@ -486,10 +485,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
if (!PackageHelper.fitsOnInternal(mContext, params)) {
throw new IOException("No suitable internal storage available");
}
- } else {
+ } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
// For now, installs to adopted media are treated as internal from
// an install flag point-of-view.
params.installFlags |= PackageManager.INSTALL_INTERNAL;
+ } else {
+ params.installFlags |= PackageManager.INSTALL_INTERNAL;
// Resolve best location for install, based on combination of
// requested install flags, delta size, and manifest settings.
@@ -736,22 +737,19 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
// Check whether the caller is device owner or affiliated profile owner, in which case we do
// it silently.
- final int callingUserId = UserHandle.getUserId(callingUid);
DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
- final boolean isDeviceOwnerOrAffiliatedProfileOwner =
- dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)
- && dpmi.isUserAffiliatedWithDevice(callingUserId);
+ final boolean canSilentlyInstallPackage =
+ dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid);
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
- isDeviceOwnerOrAffiliatedProfileOwner, userId);
+ canSilentlyInstallPackage, userId);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
- } else if (isDeviceOwnerOrAffiliatedProfileOwner) {
+ } else if (canSilentlyInstallPackage) {
// Allow the device owner and affiliated profile owner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 26a92a4cdde4..206a88bde616 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -44,7 +44,6 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.IApexService;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -194,7 +193,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/** Package of the owner of the installer session */
@GuardedBy("mLock")
- private String mInstallerPackageName;
+ private @Nullable String mInstallerPackageName;
/** Uid of the owner of the installer session */
@GuardedBy("mLock")
@@ -340,11 +339,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
@GuardedBy("mLock")
private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() {
+ if (userId != UserHandle.getUserId(mInstallerUid)) {
+ return false;
+ }
DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
- return dpmi != null && dpmi.isActiveAdminWithPolicy(mInstallerUid,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) && dpmi.isUserAffiliatedWithDevice(
- userId);
+ return dpmi != null && dpmi.canSilentlyInstallPackage(mInstallerPackageName, mInstallerUid);
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index edab94c963f1..6cfb846e3ade 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,7 +24,6 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_HOME;
@@ -304,7 +303,6 @@ import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.ArtManagerService;
-import com.android.server.pm.dex.DexLogger;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
@@ -2168,10 +2166,7 @@ public class PackageManagerService extends IPackageManager.Stub
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
- DexManager.Listener dexManagerListener = DexLogger.getListener(this,
- installer, mInstallLock);
- mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock,
- dexManagerListener);
+ mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock);
mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
@@ -9215,7 +9210,7 @@ public class PackageManagerService extends IPackageManager.Stub
/**
* Reconcile the information we have about the secondary dex files belonging to
- * {@code packagName} and the actual dex files. For all dex files that were
+ * {@code packageName} and the actual dex files. For all dex files that were
* deleted, update the internal records and delete the generated oat files.
*/
@Override
@@ -15269,9 +15264,12 @@ public class PackageManagerService extends IPackageManager.Stub
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
if (prepareResult.replace && !prepareResult.system) {
+ final boolean killApp = (scanResult.request.scanFlags & SCAN_DONT_KILL_APP) == 0;
+ final int deleteFlags = PackageManager.DELETE_KEEP_DATA
+ | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
deletePackageAction = mayDeletePackageLocked(res.removedInfo,
prepareResult.originalPs, prepareResult.disabledPs,
- prepareResult.childPackageSettings);
+ prepareResult.childPackageSettings, deleteFlags, installArgs.user);
if (deletePackageAction == null) {
throw new ReconcileFailure(
PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
@@ -15353,12 +15351,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
} else {
- final boolean killApp = (scanRequest.scanFlags & SCAN_DONT_KILL_APP) == 0;
- final int deleteFlags = PackageManager.DELETE_KEEP_DATA
- | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
try {
executeDeletePackageLIF(reconciledPkg.deletePackageAction, packageName,
- null, true, request.mAllUsers, deleteFlags, true, pkg);
+ true, request.mAllUsers, true, pkg);
} catch (SystemDeleteException e) {
if (Build.IS_ENG) {
throw new RuntimeException("Unexpected failure", e);
@@ -17818,12 +17813,23 @@ public class PackageManagerService extends IPackageManager.Stub
public final PackageSetting deletingPs;
public final PackageSetting disabledPs;
public final PackageRemovedInfo outInfo;
+ public final int flags;
+ public final UserHandle user;
+ /**
+ * True if this package is an unupdated system app that may be deleted by the system.
+ * When true, disabledPs will be null.
+ */
+ public final boolean mayDeleteUnupdatedSystemApp;
private DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs,
- PackageRemovedInfo outInfo) {
+ PackageRemovedInfo outInfo, int flags, UserHandle user,
+ boolean mayDeleteUnupdatedSystemApp) {
this.deletingPs = deletingPs;
this.disabledPs = disabledPs;
this.outInfo = outInfo;
+ this.flags = flags;
+ this.user = user;
+ this.mayDeleteUnupdatedSystemApp = mayDeleteUnupdatedSystemApp;
}
}
@@ -17835,23 +17841,26 @@ public class PackageManagerService extends IPackageManager.Stub
@GuardedBy("mPackages")
private static DeletePackageAction mayDeletePackageLocked(
PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs,
- @Nullable PackageSetting[] children) {
+ @Nullable PackageSetting[] children, int flags, UserHandle user) {
if (ps == null) {
return null;
}
+ boolean mayDeleteUnupdatedSystemApp = false;
if (isSystemApp(ps)) {
if (ps.parentPackageName != null) {
Slog.w(TAG, "Attempt to delete child system package " + ps.pkg.packageName);
return null;
}
- // Confirm if the system package has been updated
- // An updated system app can be deleted. This will also have to restore
- // the system pkg from system partition
- // reader
- if (disabledPs == null) {
- Slog.w(TAG,
- "Attempt to delete unknown system package " + ps.pkg.packageName);
+ if (((flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
+ && user.getIdentifier() != UserHandle.USER_ALL) {
+ mayDeleteUnupdatedSystemApp = true;
+ } else if (disabledPs == null) {
+ // Confirmed if the system package has been updated
+ // An updated system app can be deleted. This will also have to restore
+ // the system pkg from system partition
+ // reader
+ Slog.w(TAG, "Attempt to delete unknown system package " + ps.pkg.packageName);
return null;
}
}
@@ -17868,7 +17877,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
}
- return new DeletePackageAction(ps, disabledPs, outInfo);
+ return new DeletePackageAction(ps, disabledPs, outInfo, flags, user,
+ mayDeleteUnupdatedSystemApp);
}
/*
@@ -17883,7 +17893,7 @@ public class PackageManagerService extends IPackageManager.Stub
final PackageSetting ps = mSettings.mPackages.get(packageName);
final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps);
PackageSetting[] children = mSettings.getChildSettingsLPr(ps);
- action = mayDeletePackageLocked(outInfo, ps, disabledPs, children);
+ action = mayDeletePackageLocked(outInfo, ps, disabledPs, children, flags, user);
}
if (null == action) {
return false;
@@ -17892,8 +17902,8 @@ public class PackageManagerService extends IPackageManager.Stub
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
try {
- executeDeletePackageLIF(action, packageName, user, deleteCodeAndResources,
- allUserHandles, flags, writeSettings, replacingPackage);
+ executeDeletePackageLIF(action, packageName, deleteCodeAndResources,
+ allUserHandles, writeSettings, replacingPackage);
} catch (SystemDeleteException e) {
return false;
}
@@ -17910,11 +17920,13 @@ public class PackageManagerService extends IPackageManager.Stub
/** Deletes a package. Only throws when install of a disabled package fails. */
private void executeDeletePackageLIF(DeletePackageAction action,
- String packageName, UserHandle user, boolean deleteCodeAndResources,
- int[] allUserHandles, int flags, boolean writeSettings,
+ String packageName, boolean deleteCodeAndResources,
+ int[] allUserHandles, boolean writeSettings,
PackageParser.Package replacingPackage) throws SystemDeleteException {
final PackageSetting ps = action.deletingPs;
final PackageRemovedInfo outInfo = action.outInfo;
+ final UserHandle user = action.user;
+ final int flags = action.flags;
final boolean systemApp = isSystemApp(ps);
synchronized (mPackages) {
@@ -17940,8 +17952,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
- if (((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
- && user.getIdentifier() != UserHandle.USER_ALL)) {
+ if (!systemApp || action.mayDeleteUnupdatedSystemApp) {
// The caller is asking that the package only be deleted for a single
// user. To do this, we just mark its uninstalled state and delete
// its data. If this is a system app, we only allow this to happen if
@@ -20169,11 +20180,6 @@ public class PackageManagerService extends IPackageManager.Stub
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
- if (StorageManager.hasIsolatedStorage()) {
- return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED
- ? Zygote.MOUNT_EXTERNAL_FULL
- : Zygote.MOUNT_EXTERNAL_WRITE;
- }
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 88d9e52ccf51..68a755b382ca 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -18,29 +18,32 @@ package com.android.server.pm.dex;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.os.FileUtils;
import android.os.RemoteException;
-
-import android.util.ArraySet;
+import android.os.storage.StorageManager;
import android.util.ByteStringUtils;
import android.util.EventLog;
import android.util.PackageUtils;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile;
+import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
import java.io.File;
+import java.util.Map;
import java.util.Set;
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
-
/**
* This class is responsible for logging data about secondary dex files.
* The data logged includes hashes of the name and content of each file.
*/
-public class DexLogger implements DexManager.Listener {
+public class DexLogger {
private static final String TAG = "DexLogger";
// Event log tag & subtag used for SafetyNet logging of dynamic
@@ -49,75 +52,172 @@ public class DexLogger implements DexManager.Listener {
private static final String DCL_SUBTAG = "dcl";
private final IPackageManager mPackageManager;
+ private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
- public static DexManager.Listener getListener(IPackageManager pms,
- Installer installer, Object installLock) {
- return new DexLogger(pms, installer, installLock);
+ public DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+ this(pms, installer, installLock, new PackageDynamicCodeLoading());
}
@VisibleForTesting
- /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+ DexLogger(IPackageManager pms, Installer installer, Object installLock,
+ PackageDynamicCodeLoading packageDynamicCodeLoading) {
mPackageManager = pms;
+ mPackageDynamicCodeLoading = packageDynamicCodeLoading;
mInstaller = installer;
mInstallLock = installLock;
}
+ public Set<String> getAllPackagesWithDynamicCodeLoading() {
+ return mPackageDynamicCodeLoading.getAllPackagesWithDynamicCodeLoading();
+ }
+
/**
- * Compute and log hashes of the name and content of a secondary dex file.
+ * Write information about code dynamically loaded by {@code packageName} to the event log.
*/
- @Override
- public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
- String dexPath, int storageFlags) {
- int ownerUid = appInfo.uid;
-
- byte[] hash = null;
- synchronized(mInstallLock) {
- try {
- hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
- ownerUid, appInfo.volumeUuid, storageFlags);
- } catch (InstallerException e) {
- Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
- " : " + e.getMessage());
- }
- }
- if (hash == null) {
+ public void logDynamicCodeLoading(String packageName) {
+ PackageDynamicCode info = getPackageDynamicCodeInfo(packageName);
+ if (info == null) {
return;
}
- String dexFileName = new File(dexPath).getName();
- String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
- // Valid SHA256 will be 256 bits, 32 bytes.
- if (hash.length == 32) {
- message = message + ' ' + ByteStringUtils.toHexString(hash);
- }
+ SparseArray<ApplicationInfo> appInfoByUser = new SparseArray<>();
+ boolean needWrite = false;
+
+ for (Map.Entry<String, DynamicCodeFile> fileEntry : info.mFileUsageMap.entrySet()) {
+ String filePath = fileEntry.getKey();
+ DynamicCodeFile fileInfo = fileEntry.getValue();
+ int userId = fileInfo.mUserId;
- writeDclEvent(ownerUid, message);
+ int index = appInfoByUser.indexOfKey(userId);
+ ApplicationInfo appInfo;
+ if (index >= 0) {
+ appInfo = appInfoByUser.get(userId);
+ } else {
+ appInfo = null;
- if (dexUseInfo.isUsedByOtherApps()) {
- Set<String> otherPackages = dexUseInfo.getLoadingPackages();
- Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
- for (String otherPackageName : otherPackages) {
try {
- int otherUid = mPackageManager.getPackageUid(
- otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
- if (otherUid != -1 && otherUid != ownerUid) {
- otherUids.add(otherUid);
- }
- } catch (RemoteException ignore) {
+ PackageInfo ownerInfo =
+ mPackageManager.getPackageInfo(packageName, /*flags*/ 0, userId);
+ appInfo = ownerInfo == null ? null : ownerInfo.applicationInfo;
+ } catch (RemoteException ignored) {
// Can't happen, we're local.
}
+ appInfoByUser.put(userId, appInfo);
+ if (appInfo == null) {
+ Slog.d(TAG, "Could not find package " + packageName + " for user " + userId);
+ // Package has probably been uninstalled for user.
+ needWrite |= mPackageDynamicCodeLoading.removeUserPackage(packageName, userId);
+ }
+ }
+
+ if (appInfo == null) {
+ continue;
}
- for (int otherUid : otherUids) {
- writeDclEvent(otherUid, message);
+
+ int storageFlags;
+ if (appInfo.deviceProtectedDataDir != null
+ && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) {
+ storageFlags = StorageManager.FLAG_STORAGE_DE;
+ } else if (appInfo.credentialProtectedDataDir != null
+ && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) {
+ storageFlags = StorageManager.FLAG_STORAGE_CE;
+ } else {
+ Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath);
+ needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
+ continue;
+ }
+
+ byte[] hash = null;
+ synchronized (mInstallLock) {
+ try {
+ hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid,
+ appInfo.volumeUuid, storageFlags);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Got InstallerException when hashing file " + filePath
+ + ": " + e.getMessage());
+ }
+ }
+
+ String fileName = new File(filePath).getName();
+ String message = PackageUtils.computeSha256Digest(fileName.getBytes());
+
+ // Valid SHA256 will be 256 bits, 32 bytes.
+ if (hash != null && hash.length == 32) {
+ message = message + ' ' + ByteStringUtils.toHexString(hash);
+ } else {
+ Slog.d(TAG, "Got no hash for " + filePath);
+ // File has probably been deleted.
+ needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
+ }
+
+ for (String loadingPackageName : fileInfo.mLoadingPackages) {
+ int loadingUid = -1;
+ if (loadingPackageName.equals(packageName)) {
+ loadingUid = appInfo.uid;
+ } else {
+ try {
+ loadingUid = mPackageManager.getPackageUid(loadingPackageName, /*flags*/ 0,
+ userId);
+ } catch (RemoteException ignored) {
+ // Can't happen, we're local.
+ }
+ }
+
+ if (loadingUid != -1) {
+ writeDclEvent(loadingUid, message);
+ }
}
}
+
+ if (needWrite) {
+ mPackageDynamicCodeLoading.maybeWriteAsync();
+ }
+ }
+
+ @VisibleForTesting
+ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
+ return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
}
@VisibleForTesting
- /*package*/ void writeDclEvent(int uid, String message) {
+ void writeDclEvent(int uid, String message) {
EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
}
+
+ void record(int loaderUserId, String dexPath,
+ String owningPackageName, String loadingPackageName) {
+ if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath,
+ PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
+ loadingPackageName)) {
+ mPackageDynamicCodeLoading.maybeWriteAsync();
+ }
+ }
+
+ void clear() {
+ mPackageDynamicCodeLoading.clear();
+ }
+
+ void removePackage(String packageName) {
+ if (mPackageDynamicCodeLoading.removePackage(packageName)) {
+ mPackageDynamicCodeLoading.maybeWriteAsync();
+ }
+ }
+
+ void removeUserPackage(String packageName, int userId) {
+ if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
+ mPackageDynamicCodeLoading.maybeWriteAsync();
+ }
+ }
+
+ void readAndSync(Map<String, Set<Integer>> packageToUsersMap) {
+ mPackageDynamicCodeLoading.read();
+ mPackageDynamicCodeLoading.syncData(packageToUsersMap);
+ }
+
+ void writeNow() {
+ mPackageDynamicCodeLoading.writeNow();
+ }
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 36b7269576b6..25ef7675e2b9 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -19,7 +19,6 @@ package com.android.server.pm.dex;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
-import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
import android.content.ContentResolver;
import android.content.Context;
@@ -90,18 +89,17 @@ public class DexManager {
// encode and save the dex usage data.
private final PackageDexUsage mPackageDexUsage;
- // PackageDynamicCodeLoading handles recording of dynamic code loading -
- // which is similar to PackageDexUsage but records a different aspect of the data.
+ // DexLogger handles recording of dynamic code loading - which is similar to PackageDexUsage
+ // but records a different aspect of the data.
// (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
// record class loaders or ISAs.)
- private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
+ private final DexLogger mDexLogger;
private final IPackageManager mPackageManager;
private final PackageDexOptimizer mPackageDexOptimizer;
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
- private final Listener mListener;
// Possible outcomes of a dex search.
private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
@@ -122,26 +120,20 @@ public class DexManager {
*/
private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
- public interface Listener {
- /**
- * Invoked just before the secondary dex file {@code dexPath} for the specified application
- * is reconciled.
- */
- void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
- String dexPath, int storageFlags);
- }
-
public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
- Installer installer, Object installLock, Listener listener) {
+ Installer installer, Object installLock) {
mContext = context;
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
- mPackageDynamicCodeLoading = new PackageDynamicCodeLoading();
mPackageManager = pms;
mPackageDexOptimizer = pdo;
mInstaller = installer;
mInstallLock = installLock;
- mListener = listener;
+ mDexLogger = new DexLogger(pms, installer, installLock);
+ }
+
+ public DexLogger getDexLogger() {
+ return mDexLogger;
}
public void systemReady() {
@@ -243,11 +235,8 @@ public class DexManager {
continue;
}
- if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath,
- PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
- loadingAppInfo.packageName)) {
- mPackageDynamicCodeLoading.maybeWriteAsync();
- }
+ mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName,
+ loadingAppInfo.packageName);
if (classLoaderContexts != null) {
@@ -284,7 +273,7 @@ public class DexManager {
loadInternal(existingPackages);
} catch (Exception e) {
mPackageDexUsage.clear();
- mPackageDynamicCodeLoading.clear();
+ mDexLogger.clear();
Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
}
}
@@ -335,16 +324,12 @@ public class DexManager {
if (mPackageDexUsage.removePackage(packageName)) {
mPackageDexUsage.maybeWriteAsync();
}
- if (mPackageDynamicCodeLoading.removePackage(packageName)) {
- mPackageDynamicCodeLoading.maybeWriteAsync();
- }
+ mDexLogger.removePackage(packageName);
} else {
if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
mPackageDexUsage.maybeWriteAsync();
}
- if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
- mPackageDynamicCodeLoading.maybeWriteAsync();
- }
+ mDexLogger.removeUserPackage(packageName, userId);
}
}
@@ -423,10 +408,9 @@ public class DexManager {
}
try {
- mPackageDynamicCodeLoading.read();
- mPackageDynamicCodeLoading.syncData(packageToUsersMap);
+ mDexLogger.readAndSync(packageToUsersMap);
} catch (Exception e) {
- mPackageDynamicCodeLoading.clear();
+ mDexLogger.clear();
Slog.w(TAG, "Exception while loading package dynamic code usage. "
+ "Starting with a fresh state.", e);
}
@@ -460,11 +444,6 @@ public class DexManager {
return mPackageDexUsage.getPackageUseInfo(packageName) != null;
}
- @VisibleForTesting
- /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
- return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
- }
-
/**
* Perform dexopt on with the given {@code options} on the secondary dex files.
* @return true if all secondary dex files were processed successfully (compiled or skipped
@@ -574,10 +553,6 @@ public class DexManager {
continue;
}
- if (mListener != null) {
- mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
- }
-
boolean dexStillExists = true;
synchronized(mInstallLock) {
try {
@@ -721,7 +696,7 @@ public class DexManager {
*/
public void writePackageDexUsageNow() {
mPackageDexUsage.writeNow();
- mPackageDynamicCodeLoading.writeNow();
+ mDexLogger.writeNow();
}
private void registerSettingObserver() {
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index c5139b562065..cedb54801dc8 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -111,7 +111,7 @@ public class BatterySaverPolicy extends ContentObserver {
false, /* enableAdjustBrightness */
false, /* enableDataSaver */
true, /* enableFirewall */
- false, /* enableQuickDoze */
+ true, /* enableQuickDoze */
new ArrayMap<>(), /* filesForInteractive */
new ArrayMap<>(), /* filesForNoninteractive */
true, /* forceAllAppsStandby */
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 20e4985ddd19..244ccb69e958 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,4 +1,5 @@
michaelwr@google.com
+santoscordon@google.com
per-file BatterySaverPolicy.java=omakoto@google.com
per-file ShutdownThread.java=fkupolov@google.com
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfig.java b/services/core/java/com/android/server/signedconfig/SignedConfig.java
index e6bb800045c8..560a1e1cfe6c 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfig.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfig.java
@@ -33,19 +33,34 @@ import java.util.Set;
* Represents signed configuration.
*
* <p>This configuration should only be used if the signature has already been verified.
+ *
+ * This class also parses signed config from JSON. The format expected is:
+ * <pre>
+ * {
+ * "version": 1
+ * "config": [
+ * {
+ * "min_sdk": 28,
+ * "max_sdk": 29,
+ * "values": {
+ * "key": "value",
+ * "key2": "value2"
+ * ...
+ * }
+ * },
+ * ...
+ * ],
+ * }
+ * </pre>
*/
public class SignedConfig {
private static final String KEY_VERSION = "version";
private static final String KEY_CONFIG = "config";
- private static final String CONFIG_KEY_MIN_SDK = "minSdk";
- private static final String CONFIG_KEY_MAX_SDK = "maxSdk";
+ private static final String CONFIG_KEY_MIN_SDK = "min_sdk";
+ private static final String CONFIG_KEY_MAX_SDK = "max_sdk";
private static final String CONFIG_KEY_VALUES = "values";
- // TODO it may be better to use regular key/value pairs in a JSON object, rather than an array
- // of objects with the 2 keys below.
- private static final String CONFIG_KEY_KEY = "key";
- private static final String CONFIG_KEY_VALUE = "value";
/**
* Represents config values targeting an SDK range.
@@ -141,14 +156,10 @@ public class SignedConfig {
throws JSONException, InvalidConfigException {
int minSdk = json.getInt(CONFIG_KEY_MIN_SDK);
int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK);
- JSONArray valueArray = json.getJSONArray(CONFIG_KEY_VALUES);
+ JSONObject valuesJson = json.getJSONObject(CONFIG_KEY_VALUES);
Map<String, String> values = new HashMap<>();
- for (int i = 0; i < valueArray.length(); ++i) {
- JSONObject keyValuePair = valueArray.getJSONObject(i);
- String key = keyValuePair.getString(CONFIG_KEY_KEY);
- Object valueObject = keyValuePair.has(CONFIG_KEY_VALUE)
- ? keyValuePair.get(CONFIG_KEY_VALUE)
- : null;
+ for (String key : valuesJson.keySet()) {
+ Object valueObject = valuesJson.get(key);
String value = valueObject == JSONObject.NULL || valueObject == null
? null
: valueObject.toString();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4e9c5ab39ea8..de8024fb9ae8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -96,6 +96,7 @@ import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
import static com.android.server.am.ActivityRecordProto.VISIBLE;
import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
@@ -122,8 +123,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.ActivityTaskManagerService
- .RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -157,6 +157,7 @@ import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PipModeChangeItem;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.WindowVisibilityItem;
+import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -716,8 +717,12 @@ final class ActivityRecord extends ConfigurationContainer {
// to forcing the update of the picture-in-picture mode as a part of the PiP animation.
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
- final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
- targetStackBounds, null);
+ final Configuration newConfig = new Configuration();
+ if (targetStackBounds != null && !targetStackBounds.isEmpty()) {
+ task.computeResolvedOverrideConfiguration(newConfig,
+ task.getParent().getConfiguration(),
+ task.getRequestedOverrideConfiguration());
+ }
schedulePictureInPictureModeChanged(newConfig);
scheduleMultiWindowModeChanged(newConfig);
}
@@ -1035,8 +1040,6 @@ final class ActivityRecord extends ConfigurationContainer {
inHistory = true;
- final TaskWindowContainerController taskController = task.getWindowContainerController();
-
// TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
task.updateOverrideConfigurationFromLaunchBounds();
// Make sure override configuration is up-to-date before using to create window controller.
@@ -1048,10 +1051,9 @@ final class ActivityRecord extends ConfigurationContainer {
// TODO: Should this throw an exception instead?
Slog.w(TAG, "Attempted to add existing app token: " + appToken);
} else {
- final Task container = taskController.mContainer;
+ final Task container = task.getTask();
if (container == null) {
- throw new IllegalArgumentException("AppWindowContainerController: invalid "
- + " controller=" + taskController);
+ throw new IllegalArgumentException("createAppWindowToken: invalid task =" + task);
}
mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken,
task.voiceSession != null, container.getDisplayContent(),
@@ -1062,7 +1064,7 @@ final class ActivityRecord extends ConfigurationContainer {
mLaunchTaskBehind, isAlwaysFocusable());
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) {
Slog.v(TAG, "addAppToken: "
- + mAppWindowToken + " controller=" + taskController + " at "
+ + mAppWindowToken + " task=" + container + " at "
+ Integer.MAX_VALUE);
}
container.addChild(mAppWindowToken, Integer.MAX_VALUE /* add on top */);
@@ -1091,6 +1093,12 @@ final class ActivityRecord extends ConfigurationContainer {
Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + appToken);
return false;
}
+ if (mAppWindowToken.getTask() == null) {
+ // Can be removed after unification of Task and TaskRecord.
+ Slog.w(TAG_WM, "Attempted to start a window to an app token not having attached to any"
+ + " task: " + appToken);
+ return false;
+ }
return mAppWindowToken.addStartingWindow(pkg, theme, compatInfo, nonLocalizedLabel,
labelRes, icon, logo, windowFlags, transferFrom, newTask, taskSwitch,
processRunning, allowTaskSnapshot, activityCreated, fromRecents);
@@ -1148,7 +1156,7 @@ final class ActivityRecord extends ConfigurationContainer {
+ " r=" + this + " (" + prevTask.getStackId() + ")");
}
- mAppWindowToken.reparent(newTask.getWindowContainerController(), position);
+ mAppWindowToken.reparent(newTask.getTask(), position);
// Reparenting prevents informing the parent stack of activity removal in the case that
// the new stack has the same parent. we must manually signal here if this is not the case.
@@ -1801,6 +1809,18 @@ final class ActivityRecord extends ConfigurationContainer {
}
mAppWindowToken.detachChildren();
}
+
+ if (state == RESUMED) {
+ mAtmService.updateBatteryStats(this, true);
+ mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_RESUMED);
+ } else if (state == PAUSED) {
+ mAtmService.updateBatteryStats(this, false);
+ mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED);
+ } else if (state == STOPPED) {
+ mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+ } else if (state == DESTROYED) {
+ mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
+ }
}
ActivityState getState() {
@@ -1996,10 +2016,7 @@ final class ActivityRecord extends ConfigurationContainer {
stopped = false;
if (isActivityTypeHome()) {
- WindowProcessController app = task.mActivities.get(0).app;
- if (hasProcess() && app != mAtmService.mHomeProcess) {
- mAtmService.mHomeProcess = app;
- }
+ mStackSupervisor.updateHomeProcess(task.mActivities.get(0).app);
}
if (nowVisible) {
@@ -2542,12 +2559,10 @@ final class ActivityRecord extends ConfigurationContainer {
setBounds(mTmpBounds);
- final Rect updatedBounds = getRequestedOverrideBounds();
-
// Bounds changed...update configuration to match.
if (!matchParentBounds()) {
- task.computeOverrideConfiguration(mTmpConfig, updatedBounds,
- false /* overrideWidth */, false /* overrideHeight */);
+ task.computeResolvedOverrideConfiguration(mTmpConfig,
+ task.getParent().getConfiguration(), getRequestedOverrideConfiguration());
}
onRequestedOverrideConfigurationChanged(mTmpConfig);
@@ -2774,7 +2789,7 @@ final class ActivityRecord extends ConfigurationContainer {
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing =
- getTaskRecord().getWindowContainerController().isDragResizing();
+ getTaskRecord().getTask().isDragResizing();
mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE
: RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index aca9702a45c8..2663d997162c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -294,13 +294,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// stack and the new stack will be on top of all stacks.
static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2;
- // The height/width divide used when fitting a task within a bounds with method
- // {@link #fitWithinBounds}.
- // We always want the task to to be visible in the bounds without affecting its size when
- // fitting. To make sure this is the case, we don't adjust the task left or top side pass
- // the input bounds right or bottom side minus the width or height divided by this value.
- private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
-
final ActivityTaskManagerService mService;
private final WindowManagerService mWindowManager;
T mWindowContainerController;
@@ -365,9 +358,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
+ private boolean mUpdateDisplayedBoundsDeferredCalled;
private final Rect mDeferredBounds = new Rect();
- private final Rect mDeferredTaskBounds = new Rect();
- private final Rect mDeferredTaskInsetBounds = new Rect();
+ private final Rect mDeferredDisplayedBounds = new Rect();
int mCurrentUser;
@@ -605,7 +598,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3);
// immediately resize so docked bounds are available in onSplitScreenModeActivated
- resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+ setTaskDisplayedBounds(null);
+ setTaskBounds(mTmpRect2);
+ setBounds(mTmpRect2);
} else if (
getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
Rect dockedBounds = display.getSplitScreenPrimaryStack().getBounds();
@@ -911,7 +906,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
void positionChildWindowContainerAtTop(TaskRecord child) {
- mWindowContainerController.positionChildAtTop(child.getWindowContainerController(),
+ mWindowContainerController.positionChildAtTop(child.getTask(),
true /* includingParents */);
}
@@ -921,7 +916,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// task to bottom, the next focusable stack on the same display should be focused.
final ActivityStack nextFocusableStack = getDisplay().getNextFocusableStack(
child.getStack(), true /* ignoreCurrent */);
- mWindowContainerController.positionChildAtBottom(child.getWindowContainerController(),
+ mWindowContainerController.positionChildAtBottom(child.getTask(),
nextFocusableStack == null /* includingParents */);
}
@@ -949,17 +944,19 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* be resized to that bounds.
*/
void continueUpdateBounds() {
- final boolean wasDeferred = mUpdateBoundsDeferred;
- mUpdateBoundsDeferred = false;
- if (wasDeferred && mUpdateBoundsDeferredCalled) {
- resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds,
- mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds,
- mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds);
+ if (mUpdateBoundsDeferred) {
+ mUpdateBoundsDeferred = false;
+ if (mUpdateBoundsDeferredCalled) {
+ setTaskBounds(mDeferredBounds);
+ setBounds(mDeferredBounds);
+ }
+ if (mUpdateDisplayedBoundsDeferredCalled) {
+ setTaskDisplayedBounds(mDeferredDisplayedBounds);
+ }
}
}
- boolean updateBoundsAllowed(Rect bounds, Rect tempTaskBounds,
- Rect tempTaskInsetBounds) {
+ boolean updateBoundsAllowed(Rect bounds) {
if (!mUpdateBoundsDeferred) {
return true;
}
@@ -968,17 +965,20 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
} else {
mDeferredBounds.setEmpty();
}
- if (tempTaskBounds != null) {
- mDeferredTaskBounds.set(tempTaskBounds);
- } else {
- mDeferredTaskBounds.setEmpty();
+ mUpdateBoundsDeferredCalled = true;
+ return false;
+ }
+
+ boolean updateDisplayedBoundsAllowed(Rect bounds) {
+ if (!mUpdateBoundsDeferred) {
+ return true;
}
- if (tempTaskInsetBounds != null) {
- mDeferredTaskInsetBounds.set(tempTaskInsetBounds);
+ if (bounds != null) {
+ mDeferredDisplayedBounds.set(bounds);
} else {
- mDeferredTaskInsetBounds.setEmpty();
+ mDeferredDisplayedBounds.setEmpty();
}
- mUpdateBoundsDeferredCalled = true;
+ mUpdateDisplayedBoundsDeferredCalled = true;
return false;
}
@@ -1617,7 +1617,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
try {
EventLogTags.writeAmPauseActivity(prev.mUserId, System.identityHashCode(prev),
prev.shortComponentName, "userLeaving=" + userLeaving);
- mService.updateUsageStats(prev, false);
mService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
@@ -2984,7 +2983,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
mTaskHistory.add(position, task);
- mWindowContainerController.positionChildAt(task.getWindowContainerController(), position);
+ mWindowContainerController.positionChildAt(task.getTask(), position);
updateTaskMovement(task, true);
}
@@ -4649,9 +4648,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
r.mUserId, System.identityHashCode(r),
r.getTaskRecord().taskId, r.shortComponentName,
"proc died without state saved");
- if (r.getState() == RESUMED) {
- mService.updateUsageStats(r, false);
- }
}
} else {
// We have the current state for this activity, so
@@ -4912,7 +4908,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// TODO: Can only be called from special methods in ActivityStackSupervisor.
// Need to consolidate those calls points into this resize method so anyone can call directly.
void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
- if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
+ if (!updateBoundsAllowed(bounds)) {
return;
}
@@ -4926,20 +4922,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
final TaskRecord task = mTaskHistory.get(i);
if (task.isResizeable()) {
- if (inFreeformWindowingMode()) {
- // TODO(b/71028874): Can be removed since each freeform task is its own
- // stack.
- // For freeform stack we don't adjust the size of the tasks to match that
- // of the stack, but we do try to make sure the tasks are still contained
- // with the bounds of the stack.
- if (task.getRequestedOverrideBounds() != null) {
- mTmpRect2.set(task.getRequestedOverrideBounds());
- fitWithinBounds(mTmpRect2, bounds);
- task.updateOverrideConfiguration(mTmpRect2);
- }
- } else {
- task.updateOverrideConfiguration(taskBounds, insetBounds);
- }
+ task.updateOverrideConfiguration(taskBounds, insetBounds);
}
if (task.hasDisplayedBounds()) {
@@ -4951,7 +4934,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
- mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
setBounds(bounds);
}
@@ -4961,41 +4943,37 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
/**
- * Adjust bounds to stay within stack bounds.
- *
- * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
- * that keep them unchanged, but be contained within the stack bounds.
- *
- * @param bounds Bounds to be adjusted.
- * @param stackBounds Bounds within which the other bounds should remain.
+ * Until we can break this "set task bounds to same as stack bounds" behavior, this
+ * basically resizes both stack and task bounds to the same bounds.
*/
- private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
- if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
+ void setTaskBounds(Rect bounds) {
+ if (!updateBoundsAllowed(bounds)) {
return;
}
- if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
- final int maxRight = stackBounds.right
- - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
- int horizontalDiff = stackBounds.left - bounds.left;
- if ((horizontalDiff < 0 && bounds.left >= maxRight)
- || (bounds.left + horizontalDiff >= maxRight)) {
- horizontalDiff = maxRight - bounds.left;
+ for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+ final TaskRecord task = mTaskHistory.get(i);
+ if (task.isResizeable()) {
+ task.setBounds(bounds);
+ } else {
+ task.setBounds(null);
}
- bounds.left += horizontalDiff;
- bounds.right += horizontalDiff;
}
+ }
- if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
- final int maxBottom = stackBounds.bottom
- - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
- int verticalDiff = stackBounds.top - bounds.top;
- if ((verticalDiff < 0 && bounds.top >= maxBottom)
- || (bounds.top + verticalDiff >= maxBottom)) {
- verticalDiff = maxBottom - bounds.top;
+ /** Helper to setDisplayedBounds on all child tasks */
+ void setTaskDisplayedBounds(Rect bounds) {
+ if (!updateDisplayedBoundsAllowed(bounds)) {
+ return;
+ }
+
+ for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+ final TaskRecord task = mTaskHistory.get(i);
+ if (bounds == null || bounds.isEmpty()) {
+ task.setDisplayedBounds(null);
+ } else if (task.isResizeable()) {
+ task.setDisplayedBounds(bounds);
}
- bounds.top += verticalDiff;
- bounds.bottom += verticalDiff;
}
}
@@ -5349,7 +5327,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
&& !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
task.updateOverrideConfiguration(getRequestedOverrideBounds());
}
- task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
+ task.createTask(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
return task;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index e761ad86c770..c43fd2487470 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -179,6 +179,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
+ static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16;
// Used to indicate that windows of activities should be preserved during the resize.
static final boolean PRESERVE_WINDOWS = true;
@@ -793,7 +794,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
System.identityHashCode(r), task.taskId, r.shortComponentName);
if (r.isActivityTypeHome()) {
// Home process is the root process of the task.
- mService.mHomeProcess = task.mActivities.get(0).app;
+ updateHomeProcess(task.mActivities.get(0).app);
}
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
@@ -915,6 +916,15 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
return true;
}
+ void updateHomeProcess(WindowProcessController app) {
+ if (app != null && mService.mHomeProcess != app) {
+ if (!mHandler.hasMessages(REPORT_HOME_CHANGED_MSG)) {
+ mHandler.sendEmptyMessage(REPORT_HOME_CHANGED_MSG);
+ }
+ mService.mHomeProcess = app;
+ }
+ }
+
private void logIfTransactionTooLarge(Intent intent, Bundle icicle) {
int extrasSize = 0;
if (intent != null) {
@@ -1868,7 +1878,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
stack.addTask(task, onTop, "restoreRecentTask");
// TODO: move call for creation here and other place into Stack.addTask()
- task.createWindowContainer(onTop, true /* showForAllUsers */);
+ task.createTask(onTop, true /* showForAllUsers */);
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
"Added restored task=" + task + " to stack=" + stack);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -2040,9 +2050,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
mStoppingActivities.remove(r);
final ActivityStack stack = r.getActivityStack();
- if (mRootActivityContainer.isTopDisplayFocusedStack(stack)) {
- mService.updateUsageStats(r, true);
- }
if (stack.getDisplay().allResumedActivitiesComplete()) {
mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
// Make sure activity & window visibility should be identical
@@ -2543,7 +2550,15 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
}
} break;
+ case REPORT_HOME_CHANGED_MSG: {
+ synchronized (mService.mGlobalLock) {
+ mHandler.removeMessages(REPORT_HOME_CHANGED_MSG);
+ // Start home activities on displays with no activities.
+ mRootActivityContainer.startHomeOnEmptyDisplays("homeChanged");
+ }
+ }
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 182d1a0f9c5d..986115726efb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3799,7 +3799,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// changed, so we should reflect that check here as well.
final PinnedActivityStack stack = r.getActivityStack();
final PinnedStackWindowController windowController = stack.getWindowContainerController();
- return !windowController.isAnimatingBoundsToFullscreen();
+ return !windowController.mContainer.isAnimatingBoundsToFullscreen();
}
@Override
@@ -5234,13 +5234,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mH.post(mAmInternal::updateCpuStats);
}
- void updateUsageStats(ActivityRecord component, boolean resumed) {
- final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateUsageStats,
+ void updateBatteryStats(ActivityRecord component, boolean resumed) {
+ final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateBatteryStats,
mAmInternal, component.mActivityComponent, component.app.mUid, component.mUserId,
resumed);
mH.sendMessage(m);
}
+ void updateActivityUsageStats(ActivityRecord activity, int event) {
+ final Message m = PooledLambda.obtainMessage(
+ ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
+ activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+ mH.sendMessage(m);
+ }
+
void setBooting(boolean booting) {
mAmInternal.setBooting(booting);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c458c94b59e2..8624bff11a18 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -746,6 +746,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
@Override
void removeImmediately() {
onRemovedFromDisplay();
+ if (mActivityRecord != null) {
+ mActivityRecord.unregisterConfigurationChangeListener(this);
+ }
super.removeImmediately();
}
@@ -1175,21 +1178,14 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
}
}
- void reparent(TaskWindowContainerController taskController, int position) {
+ void reparent(Task task, int position) {
if (DEBUG_ADD_REMOVE) {
Slog.i(TAG_WM, "reparent: moving app token=" + this
- + " to task=" + taskController + " at " + position);
+ + " to task=" + task.mTaskId + " at " + position);
}
- final Task task = taskController.mContainer;
if (task == null) {
- throw new IllegalArgumentException("reparent: could not find task="
- + taskController);
+ throw new IllegalArgumentException("reparent: could not find task");
}
- reparent(task, position);
- getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
-
- void reparent(Task task, int position) {
final Task currentTask = getTask();
if (task == currentTask) {
throw new IllegalArgumentException(
@@ -1220,6 +1216,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
onDisplayChanged(displayContent);
prevDisplayContent.setLayoutNeeded();
}
+ getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
@Override
diff --git a/services/core/java/com/android/server/wm/PinnedActivityStack.java b/services/core/java/com/android/server/wm/PinnedActivityStack.java
index 1c7ebd63dfb3..2a05af4f473c 100644
--- a/services/core/java/com/android/server/wm/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/wm/PinnedActivityStack.java
@@ -77,7 +77,7 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
}
boolean isAnimatingBoundsToFullscreen() {
- return getWindowContainerController().isAnimatingBoundsToFullscreen();
+ return getWindowContainerController().mContainer.isAnimatingBoundsToFullscreen();
}
/**
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index bbdcc62ddbc0..518e39ba9d58 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
@@ -180,15 +181,6 @@ public class PinnedStackWindowController extends StackWindowController {
}
/**
- * @return whether the bounds are currently animating to fullscreen.
- */
- public boolean isAnimatingBoundsToFullscreen() {
- synchronized (mGlobalLock) {
- return mContainer.isAnimatingBoundsToFullscreen();
- }
- }
-
- /**
* @return whether the stack can be resized from the bounds animation.
*/
public boolean pinnedStackResizeDisallowed() {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c5b42f99fcda..84a32fc44c53 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -336,6 +336,15 @@ class RootActivityContainer extends ConfigurationContainer
return homeStarted;
}
+ void startHomeOnEmptyDisplays(String reason) {
+ for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
+ final ActivityDisplay display = mActivityDisplays.get(i);
+ if (display.topRunningActivity() == null) {
+ startHomeOnDisplay(mCurrentUser, reason, display.mDisplayId);
+ }
+ }
+ }
+
/**
* This starts home activity on displays that can have system decorations and only if the
* home activity can have multiple instances.
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 8f18aa56b001..ada807b1ff3c 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -21,7 +21,6 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
@@ -29,8 +28,6 @@ import android.os.Looper;
import android.os.Message;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.DisplayCutout;
-import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,11 +46,7 @@ public class StackWindowController
private final H mHandler;
- // Temp bounds only used in adjustConfigurationForBounds()
- private final Rect mTmpRect = new Rect();
- private final Rect mTmpStableInsets = new Rect();
- private final Rect mTmpNonDecorInsets = new Rect();
- private final Rect mTmpDisplayBounds = new Rect();
+ final Rect mTmpBounds = new Rect();
public StackWindowController(int stackId, StackWindowListener listener, int displayId,
boolean onTop, Rect outBounds) {
@@ -67,107 +60,87 @@ public class StackWindowController
mStackId = stackId;
mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
- synchronized (mGlobalLock) {
- final DisplayContent dc = mRoot.getDisplayContent(displayId);
- if (dc == null) {
- throw new IllegalArgumentException("Trying to add stackId=" + stackId
- + " to unknown displayId=" + displayId);
- }
-
- dc.createStack(stackId, onTop, this);
- getRawBounds(outBounds);
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ throw new IllegalArgumentException("Trying to add stackId=" + stackId
+ + " to unknown displayId=" + displayId);
}
+
+ dc.createStack(stackId, onTop, this);
+ getRawBounds(outBounds);
}
@Override
public void removeContainer() {
- synchronized (mGlobalLock) {
- if (mContainer != null) {
- mContainer.removeIfPossible();
- super.removeContainer();
- }
+ if (mContainer != null) {
+ mContainer.removeIfPossible();
+ super.removeContainer();
}
}
- public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
- + " to displayId=" + displayId);
- }
-
- final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
- if (targetDc == null) {
- throw new IllegalArgumentException("Trying to move stackId=" + mStackId
- + " to unknown displayId=" + displayId);
- }
+ void reparent(int displayId, Rect outStackBounds, boolean onTop) {
+ if (mContainer == null) {
+ throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
+ + " to displayId=" + displayId);
+ }
- targetDc.moveStackToDisplay(mContainer, onTop);
- getRawBounds(outStackBounds);
+ final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
+ if (targetDc == null) {
+ throw new IllegalArgumentException("Trying to move stackId=" + mStackId
+ + " to unknown displayId=" + displayId);
}
+
+ targetDc.moveStackToDisplay(mContainer, onTop);
+ getRawBounds(outStackBounds);
}
- public void positionChildAt(TaskWindowContainerController child, int position) {
- synchronized (mGlobalLock) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
- + " at " + position);
- if (child.mContainer == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM,
- "positionChildAt: could not find task=" + this);
- return;
+ void positionChildAt(Task child, int position) {
+ if (DEBUG_STACK) {
+ Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + " at " + position);
+ }
+ if (child == null) {
+ if (DEBUG_STACK) {
+ Slog.i(TAG_WM, "positionChildAt: could not find task=" + this);
}
- if (mContainer == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM,
- "positionChildAt: could not find stack for task=" + mContainer);
- return;
+ return;
+ }
+ if (mContainer == null) {
+ if (DEBUG_STACK) {
+ Slog.i(TAG_WM, "positionChildAt: could not find stack for task=" + mContainer);
}
- child.mContainer.positionAt(position);
- mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ return;
}
+ child.positionAt(position);
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
- public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
+ void positionChildAtTop(Task child, boolean includingParents) {
if (child == null) {
// TODO: Fix the call-points that cause this to happen.
return;
}
- synchronized (mGlobalLock) {
- final Task childTask = child.mContainer;
- if (childTask == null) {
- Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
- return;
- }
- mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
+ mContainer.positionChildAt(POSITION_TOP, child, includingParents);
- final DisplayContent displayContent = mContainer.getDisplayContent();
- if (displayContent.mAppTransition.isTransitionSet()) {
- childTask.setSendingToBottom(false);
- }
- displayContent.layoutAndAssignWindowLayersIfNeeded();
+ final DisplayContent displayContent = mContainer.getDisplayContent();
+ if (displayContent.mAppTransition.isTransitionSet()) {
+ child.setSendingToBottom(false);
}
+ displayContent.layoutAndAssignWindowLayersIfNeeded();
}
- public void positionChildAtBottom(TaskWindowContainerController child,
- boolean includingParents) {
+ void positionChildAtBottom(Task child, boolean includingParents) {
if (child == null) {
// TODO: Fix the call-points that cause this to happen.
return;
}
- synchronized (mGlobalLock) {
- final Task childTask = child.mContainer;
- if (childTask == null) {
- Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
- return;
- }
- mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
+ mContainer.positionChildAt(POSITION_BOTTOM, child, includingParents);
- if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) {
- childTask.setSendingToBottom(true);
- }
- mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) {
+ child.setSendingToBottom(true);
}
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
/**
@@ -179,24 +152,20 @@ public class StackWindowController
*/
public void resize(Rect bounds, SparseArray<Rect> taskBounds,
SparseArray<Rect> taskTempInsetBounds) {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
- }
- // We might trigger a configuration change. Save the current task bounds for freezing.
- mContainer.prepareFreezingTaskBounds();
- if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
- && mContainer.isVisible()) {
- mContainer.getDisplayContent().setLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
- }
+ if (mContainer == null) {
+ throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
+ }
+ // We might trigger a configuration change. Save the current task bounds for freezing.
+ mContainer.prepareFreezingTaskBounds();
+ if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
+ && mContainer.isVisible()) {
+ mContainer.getDisplayContent().setLayoutNeeded();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
}
}
public void onPipAnimationEndResize() {
- synchronized (mService.mGlobalLock) {
- mContainer.onPipAnimationEndResize();
- }
+ mContainer.onPipAnimationEndResize();
}
/**
@@ -205,167 +174,37 @@ public class StackWindowController
public void getStackDockedModeBounds(Configuration parentConfig, Rect dockedBounds,
Rect currentTempTaskBounds,
Rect outStackBounds, Rect outTempTaskBounds) {
- synchronized (mGlobalLock) {
- if (mContainer != null) {
- mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds,
- currentTempTaskBounds, outStackBounds, outTempTaskBounds);
- return;
- }
- outStackBounds.setEmpty();
- outTempTaskBounds.setEmpty();
+ if (mContainer != null) {
+ mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds,
+ currentTempTaskBounds, outStackBounds, outTempTaskBounds);
+ return;
}
+ outStackBounds.setEmpty();
+ outTempTaskBounds.setEmpty();
}
public void prepareFreezingTaskBounds() {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
- + " not found.");
- }
- mContainer.prepareFreezingTaskBounds();
+ if (mContainer == null) {
+ throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
+ + " not found.");
}
+ mContainer.prepareFreezingTaskBounds();
}
public void getRawBounds(Rect outBounds) {
- synchronized (mGlobalLock) {
- if (mContainer.matchParentBounds()) {
- outBounds.setEmpty();
- } else {
- mContainer.getRawBounds(outBounds);
- }
- }
- }
-
- public void getBounds(Rect outBounds) {
- synchronized (mGlobalLock) {
- if (mContainer != null) {
- mContainer.getBounds(outBounds);
- return;
- }
+ if (mContainer.matchParentBounds()) {
outBounds.setEmpty();
+ } else {
+ mContainer.getRawBounds(outBounds);
}
}
- /**
- * Adjusts the screen size in dp's for the {@param config} for the given params. The provided
- * params represent the desired state of a configuration change. Since this utility is used
- * before mContainer has been updated, any relevant properties (like {@param windowingMode})
- * need to be passed in.
- */
- public void adjustConfigurationForBounds(Rect bounds,
- Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
- boolean overrideHeight, float density, Configuration config,
- Configuration parentConfig, int windowingMode) {
- synchronized (mGlobalLock) {
- final TaskStack stack = mContainer;
- final DisplayContent displayContent = stack.getDisplayContent();
- final DisplayInfo di = displayContent.getDisplayInfo();
- final DisplayCutout displayCutout = di.displayCutout;
- final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
-
- // Get the insets and display bounds
- displayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
- displayCutout, mTmpStableInsets);
- displayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
- displayCutout, mTmpNonDecorInsets);
- mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
-
- int width;
- int height;
-
- final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
-
- config.windowConfiguration.setBounds(bounds);
- config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
- boolean intersectParentBounds = false;
-
- if (WindowConfiguration.isFloating(windowingMode)) {
- // Floating tasks should not be resized to the screen's bounds.
-
- if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED
- && bounds.width() == mTmpDisplayBounds.width()
- && bounds.height() == mTmpDisplayBounds.height()) {
- // If the bounds we are animating is the same as the fullscreen stack
- // dimensions, then apply the same inset calculations that we normally do for
- // the fullscreen stack, without intersecting it with the display bounds
- stableBounds.inset(mTmpStableInsets);
- nonDecorBounds.inset(mTmpNonDecorInsets);
- // Move app bounds to zero to apply intersection with parent correctly. They are
- // used only for evaluating width and height, so it's OK to move them around.
- config.windowConfiguration.getAppBounds().offsetTo(0, 0);
- intersectParentBounds = true;
- }
- width = (int) (stableBounds.width() / density);
- height = (int) (stableBounds.height() / density);
- } else {
- // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
- // area, i.e. the screen area without the system bars.
- // Additionally task dimensions should not be bigger than its parents dimensions.
- // The non decor inset are areas that could never be removed in Honeycomb. See
- // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- intersectDisplayBoundsExcludeInsets(nonDecorBounds, bounds, mTmpNonDecorInsets,
- mTmpDisplayBounds, overrideWidth, overrideHeight);
- intersectDisplayBoundsExcludeInsets(stableBounds, bounds, mTmpStableInsets,
- mTmpDisplayBounds, overrideWidth, overrideHeight);
- width = Math.min((int) (stableBounds.width() / density),
- parentConfig.screenWidthDp);
- height = Math.min((int) (stableBounds.height() / density),
- parentConfig.screenHeightDp);
- intersectParentBounds = true;
- }
-
- if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) {
- config.windowConfiguration.getAppBounds().intersect(parentAppBounds);
- }
-
- config.screenWidthDp = width;
- config.screenHeightDp = height;
- config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
- bounds, density, windowingMode);
- }
- }
-
- /**
- * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
- * inset areas.
- *
- * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
- */
- private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds,
- Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) {
- mTmpRect.set(inInsetBounds);
- mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect);
- int leftInset = mTmpRect.left - inInsetBounds.left;
- int topInset = mTmpRect.top - inInsetBounds.top;
- int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right;
- int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom;
- inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
- }
-
- /**
- * Calculates the smallest width for a task given the target {@param bounds} and
- * {@param windowingMode}. Avoid using values from mContainer since they can be out-of-date.
- *
- * @return the smallest width to be used in the Configuration, in dips
- */
- private int getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode) {
- final DisplayContent displayContent = mContainer.getDisplayContent();
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
- if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
- bounds.height() == displayInfo.logicalHeight)) {
- // If the bounds are fullscreen, return the value of the fullscreen configuration
- return displayContent.getConfiguration().smallestScreenWidthDp;
- } else if (WindowConfiguration.isFloating(windowingMode)) {
- // For floating tasks, calculate the smallest width from the bounds of the task
- return (int) (Math.min(bounds.width(), bounds.height()) / density);
- } else {
- // Iterating across all screen orientations, and return the minimum of the task
- // width taking into account that the bounds might change because the snap algorithm
- // snaps to a different value
- return displayContent.getDockedDividerController()
- .getSmallestWidthDpForBounds(bounds);
+ public void getBounds(Rect outBounds) {
+ if (mContainer != null) {
+ mContainer.getBounds(outBounds);
+ return;
}
+ outBounds.setEmpty();
}
void requestResize(Rect bounds) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 67657d0427ba..b10fd31ba7ad 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.EMPTY;
import static com.android.server.EventLogTags.WM_TASK_REMOVED;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS;
import static com.android.server.wm.TaskProto.BOUNDS;
import static com.android.server.wm.TaskProto.DEFER_REMOVAL;
@@ -38,6 +39,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.CallSuper;
+import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -54,7 +56,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.function.Consumer;
-class Task extends WindowContainer<AppWindowToken> {
+class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// TODO: Track parent marks like this in WindowContainer.
@@ -109,16 +111,24 @@ class Task extends WindowContainer<AppWindowToken> {
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
+ // TODO: remove after unification
+ TaskRecord mTaskRecord;
+
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
boolean supportsPictureInPicture, TaskDescription taskDescription,
- TaskWindowContainerController controller) {
+ TaskRecord taskRecord) {
super(service);
mTaskId = taskId;
mStack = stack;
mUserId = userId;
mResizeMode = resizeMode;
mSupportsPictureInPicture = supportsPictureInPicture;
- setController(controller);
+ mTaskRecord = taskRecord;
+ if (mTaskRecord != null) {
+ // This can be null when we call createTaskInStack in WindowTestUtils. Remove this after
+ // unification.
+ mTaskRecord.registerConfigurationChangeListener(this);
+ }
setBounds(getRequestedOverrideBounds());
mTaskDescription = taskDescription;
@@ -191,10 +201,28 @@ class Task extends WindowContainer<AppWindowToken> {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
mDeferRemoval = false;
+ if (mTaskRecord != null) {
+ mTaskRecord.unregisterConfigurationChangeListener(this);
+ }
super.removeImmediately();
}
+ void reparent(StackWindowController stackController, int position, boolean moveParents) {
+ if (DEBUG_STACK) {
+ Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
+ + " to stack=" + stackController + " at " + position);
+ }
+ final TaskStack stack = stackController.mContainer;
+ if (stack == null) {
+ throw new IllegalArgumentException("reparent: could not find stack="
+ + stackController);
+ }
+ reparent(stack, position, moveParents);
+ getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ }
+
+
void reparent(TaskStack stack, int position, boolean moveParents) {
if (stack == mStack) {
throw new IllegalArgumentException(
@@ -300,6 +328,12 @@ class Task extends WindowContainer<AppWindowToken> {
return boundsChange;
}
+ void resize(boolean relayout, boolean forced) {
+ if (setBounds(getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE && relayout) {
+ getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ }
+ }
+
@Override
void onDisplayChanged(DisplayContent dc) {
adjustBoundsForDisplayChangeIfNeeded(dc);
@@ -515,6 +549,15 @@ class Task extends WindowContainer<AppWindowToken> {
return mDragResizeMode;
}
+ /**
+ * Puts this task into docked drag resizing mode. See {@link DragResizeMode}.
+ *
+ * @param resizing Whether to put the task into drag resize mode.
+ */
+ public void setTaskDockedResizing(boolean resizing) {
+ setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+ }
+
private void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
if (displayContent == null) {
return;
@@ -556,9 +599,8 @@ class Task extends WindowContainer<AppWindowToken> {
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) {
- final TaskWindowContainerController controller = getController();
- if (controller != null) {
- controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+ if (mTaskRecord != null) {
+ mTaskRecord.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
}
}
}
@@ -631,6 +673,20 @@ class Task extends WindowContainer<AppWindowToken> {
return null;
}
+ void positionChildAtTop(AppWindowToken aToken) {
+ positionChildAt(aToken, POSITION_TOP);
+ }
+
+ void positionChildAt(AppWindowToken aToken, int position) {
+ if (aToken == null) {
+ Slog.w(TAG_WM,
+ "Attempted to position of non-existing app");
+ return;
+ }
+
+ positionChildAt(position, aToken, false /* includeParents */);
+ }
+
boolean isFullscreen() {
if (useCurrentBounds()) {
return matchParentBounds();
@@ -656,6 +712,10 @@ class Task extends WindowContainer<AppWindowToken> {
mTaskDescription = taskDescription;
}
+ void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) {
+ mTaskRecord.onSnapshotChanged(snapshot);
+ }
+
TaskDescription getTaskDescription() {
return mTaskDescription;
}
@@ -666,11 +726,6 @@ class Task extends WindowContainer<AppWindowToken> {
}
@Override
- TaskWindowContainerController getController() {
- return (TaskWindowContainerController) super.getController();
- }
-
- @Override
void forAllTasks(Consumer<Task> callback) {
callback.accept(this);
}
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index b6a60090153c..5bb64407a28d 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -27,6 +27,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
@@ -47,6 +48,7 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.EventLogTags.WM_TASK_CREATED;
import static com.android.server.am.TaskRecordProto.ACTIVITIES;
import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE;
import static com.android.server.am.TaskRecordProto.BOUNDS;
@@ -76,10 +78,15 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECEN
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static java.lang.Integer.MAX_VALUE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
@@ -106,8 +113,10 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
+import android.util.EventLog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
@@ -125,8 +134,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
-// TODO: Make package private again once move to WM package is complete.
-public class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
+class TaskRecord extends ConfigurationContainer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_ATM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
@@ -190,6 +198,13 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
// Do not move the stack as a part of reparenting
static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+ // The height/width divide used when fitting a task within a bounds with method
+ // {@link #fitWithinBounds}.
+ // We always want the task to to be visible in the bounds without affecting its size when
+ // fitting. To make sure this is the case, we don't adjust the task left or top side pass
+ // the input bounds right or bottom side minus the width or height divided by this value.
+ private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
+
/**
* The factory used to create {@link TaskRecord}. This allows OEM subclass {@link TaskRecord}.
*/
@@ -295,7 +310,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
private final Rect mTmpStableBounds = new Rect();
private final Rect mTmpNonDecorBounds = new Rect();
- private final Rect mTmpRect = new Rect();
+ private final Rect mTmpBounds = new Rect();
+ private final Rect mTmpInsets = new Rect();
// Last non-fullscreen bounds the task was launched in or resized to.
// The information is persisted and used to determine the appropriate stack to launch the
@@ -318,7 +334,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
/** Helper object used for updating override configuration. */
private Configuration mTmpConfig = new Configuration();
- private TaskWindowContainerController mWindowContainerController;
+ // TODO: remove after unification
+ Task mTask;
/**
* Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
@@ -424,43 +441,54 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
mService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
}
- TaskWindowContainerController getWindowContainerController() {
- return mWindowContainerController;
+ Task getTask() {
+ return mTask;
}
- void createWindowContainer(boolean onTop, boolean showForAllUsers) {
- if (mWindowContainerController != null) {
- throw new IllegalArgumentException("Window container=" + mWindowContainerController
+ void createTask(boolean onTop, boolean showForAllUsers) {
+ if (mTask != null) {
+ throw new IllegalArgumentException("mTask=" + mTask
+ " already created for task=" + this);
}
final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
- setWindowContainerController(new TaskWindowContainerController(taskId, this,
- getStack().getWindowContainerController(), userId, bounds,
- mResizeMode, mSupportsPictureInPicture, onTop,
- showForAllUsers, lastTaskDescription));
- }
+ final StackWindowController stackController = getStack().getWindowContainerController();
- /**
- * Should only be invoked from {@link #createWindowContainer(boolean, boolean)}.
- */
- @VisibleForTesting
- protected void setWindowContainerController(TaskWindowContainerController controller) {
- if (mWindowContainerController != null) {
- throw new IllegalArgumentException("Window container=" + mWindowContainerController
- + " already created for task=" + this);
+ if (DEBUG_STACK) {
+ Slog.i(TAG_WM, "TaskRecord: taskId=" + taskId
+ + " stack=" + stackController + " bounds=" + bounds);
+ }
+
+ final TaskStack stack = stackController.mContainer;
+ if (stack == null) {
+ throw new IllegalArgumentException("TaskRecord: invalid stack="
+ + stackController);
}
+ EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
+ mTask = new Task(taskId, stack, userId, mService.mWindowManager, mResizeMode,
+ mSupportsPictureInPicture, lastTaskDescription, this);
+ final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
- mWindowContainerController = controller;
- if (!mDisplayedBounds.isEmpty() && controller.mContainer != null) {
- controller.mContainer.setOverrideDisplayedBounds(mDisplayedBounds);
+ if (!mDisplayedBounds.isEmpty()) {
+ mTask.setOverrideDisplayedBounds(mDisplayedBounds);
}
+ // We only want to move the parents to the parents if we are creating this task at the
+ // top of its stack.
+ stack.addTask(mTask, position, showForAllUsers, onTop /* moveParents */);
+ }
+
+ void setTask(Task task) {
+ mTask = task;
}
void removeWindowContainer() {
mService.getLockTaskController().clearLockedTask(this);
- mWindowContainerController.removeContainer();
- mWindowContainerController = null;
+ if (mTask == null) {
+ if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId);
+ return;
+ }
+ mTask.removeIfPossible();
+ mTask = null;
if (!getWindowConfiguration().persistTaskBounds()) {
// Reset current bounds for task whose bounds shouldn't be persisted so it uses
// default configuration the next time it launches.
@@ -469,7 +497,6 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
mService.getTaskChangeNotificationController().notifyTaskRemoved(taskId);
}
- @Override
public void onSnapshotChanged(TaskSnapshot snapshot) {
mService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(taskId, snapshot);
}
@@ -479,17 +506,20 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
return;
}
mResizeMode = resizeMode;
- mWindowContainerController.setResizeable(resizeMode);
+ mTask.setResizeable(resizeMode);
mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
mService.mRootActivityContainer.resumeFocusedStacksTopActivities();
}
void setTaskDockedResizing(boolean resizing) {
- mWindowContainerController.setTaskDockedResizing(resizing);
+ if (mTask == null) {
+ Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + taskId + " not found.");
+ return;
+ }
+ mTask.setTaskDockedResizing(resizing);
}
// TODO: Consolidate this with the resize() method below.
- @Override
public void requestResize(Rect bounds, int resizeMode) {
mService.resizeTask(taskId, bounds, resizeMode);
}
@@ -511,7 +541,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
return true;
}
- if (mWindowContainerController == null) {
+ if (mTask == null) {
// Task doesn't exist in window manager yet (e.g. was restored from recents).
// All we can do for now is update the bounds so it can be used when the task is
// added to window manager.
@@ -558,7 +588,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
}
}
}
- mWindowContainerController.resize(kept, forced);
+ mTask.resize(kept, forced);
saveLaunchingStateIfNeeded();
@@ -571,11 +601,15 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
// TODO: Investigate combining with the resize() method above.
void resizeWindowContainer() {
- mWindowContainerController.resize(false /* relayout */, false /* forced */);
+ mTask.resize(false /* relayout */, false /* forced */);
}
void getWindowContainerBounds(Rect bounds) {
- mWindowContainerController.getBounds(bounds);
+ if (mTask != null) {
+ mTask.getBounds(bounds);
+ } else {
+ bounds.setEmpty();
+ }
}
/**
@@ -679,7 +713,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
// Must reparent first in window manager to avoid a situation where AM can delete the
// we are coming from in WM before we reparent because it became empty.
- mWindowContainerController.reparent(toStack.getWindowContainerController(), position,
+ mTask.reparent(toStack.getWindowContainerController(), position,
moveStackMode == REPARENT_MOVE_STACK_TO_FRONT);
final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
@@ -779,7 +813,11 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
}
void cancelWindowTransition() {
- mWindowContainerController.cancelWindowTransition();
+ if (mTask == null) {
+ Slog.w(TAG_WM, "cancelWindowTransition: taskId " + taskId + " not found.");
+ return;
+ }
+ mTask.cancelTaskWindowTransition();
}
/**
@@ -1190,7 +1228,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
mActivities.add(newTop);
// Make sure window manager is aware of the position change.
- mWindowContainerController.positionChildAtTop(newTop.mAppWindowToken);
+ mTask.positionChildAtTop(newTop.mAppWindowToken);
updateEffectiveIntent();
setFrontOfTask();
@@ -1275,7 +1313,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
if (r.mAppWindowToken != null) {
// Only attempt to move in WM if the child has a controller. It is possible we haven't
// created controller for the activity we are starting yet.
- mWindowContainerController.positionChildAt(r.mAppWindowToken, index);
+ mTask.positionChildAt(r.mAppWindowToken, index);
}
// Make sure the list of display UID whitelists is updated
@@ -1643,8 +1681,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
}
lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename,
colorPrimary, colorBackground, statusBarColor, navigationBarColor);
- if (mWindowContainerController != null) {
- mWindowContainerController.setTaskDescription(lastTaskDescription);
+ if (mTask != null) {
+ mTask.setTaskDescription(lastTaskDescription);
}
// Update the task affiliation color if we are the parent of the group
if (taskId == mAffiliatedTaskId) {
@@ -1687,7 +1725,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
// If the task has no requested minimal size, we'd like to enforce a minimal size
// so that the user can not render the task too small to manipulate. We don't need
// to do this for the pinned stack as the bounds are controlled by the system.
- if (!inPinnedWindowingMode()) {
+ if (!inPinnedWindowingMode() && mStack != null) {
final int defaultMinSizeDp =
mService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp;
final ActivityDisplay display =
@@ -1731,31 +1769,6 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
}
/**
- * @return a new Configuration for this Task, given the provided {@param bounds} and
- * {@param insetBounds}.
- */
- Configuration computeNewOverrideConfigurationForBounds(Rect bounds, Rect insetBounds) {
- // Compute a new override configuration for the given bounds, if fullscreen bounds
- // (bounds == null), then leave the override config unset
- final Configuration newOverrideConfig = new Configuration();
- if (bounds != null) {
- newOverrideConfig.setTo(getRequestedOverrideConfiguration());
- if (insetBounds != null && !insetBounds.isEmpty()) {
- mTmpRect.set(insetBounds);
- setDisplayedBounds(bounds);
- } else {
- mTmpRect.set(bounds);
- setDisplayedBounds(null);
- }
- adjustForMinimalTaskDimensions(mTmpRect);
- computeOverrideConfiguration(newOverrideConfig, mTmpRect,
- mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
- }
-
- return newOverrideConfig;
- }
-
- /**
* Update task's override configuration based on the bounds.
* @param bounds The bounds of the task.
* @return True if the override configuration was updated.
@@ -1781,42 +1794,21 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
* @return True if the override configuration was updated.
*/
boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
- if (equivalentRequestedOverrideBounds(bounds)) {
+ final boolean hasSetDisplayedBounds = (insetBounds != null && !insetBounds.isEmpty());
+ if (hasSetDisplayedBounds) {
+ setDisplayedBounds(bounds);
+ } else {
+ setDisplayedBounds(null);
+ }
+ // "steady" bounds do not include any temporary offsets from animation or interaction.
+ Rect steadyBounds = hasSetDisplayedBounds ? insetBounds : bounds;
+ if (equivalentRequestedOverrideBounds(steadyBounds)) {
return false;
}
- final Rect currentBounds = getRequestedOverrideBounds();
-
- mTmpConfig.setTo(getRequestedOverrideConfiguration());
- final Configuration newConfig = getRequestedOverrideConfiguration();
-
- final boolean matchParentBounds = bounds == null || bounds.isEmpty();
- final boolean persistBounds = getWindowConfiguration().persistTaskBounds();
- if (matchParentBounds) {
- if (!currentBounds.isEmpty() && persistBounds) {
- setLastNonFullscreenBounds(currentBounds);
- }
- setBounds(null);
- setDisplayedBounds(null);
- newConfig.unset();
- } else {
- if (insetBounds != null && !insetBounds.isEmpty()) {
- mTmpRect.set(insetBounds);
- setDisplayedBounds(bounds);
- } else {
- mTmpRect.set(bounds);
- setDisplayedBounds(null);
- }
- adjustForMinimalTaskDimensions(mTmpRect);
- setBounds(mTmpRect);
- if (mStack == null || persistBounds) {
- setLastNonFullscreenBounds(getRequestedOverrideBounds());
- }
- computeOverrideConfiguration(newConfig, mTmpRect,
- mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
- }
- onRequestedOverrideConfigurationChanged(newConfig);
- return !mTmpConfig.equals(newConfig);
+ mTmpConfig.setTo(getResolvedOverrideConfiguration());
+ setBounds(steadyBounds);
+ return !mTmpConfig.equals(getResolvedOverrideConfiguration());
}
/**
@@ -1842,6 +1834,12 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
if (wasInMultiWindowMode != inMultiWindowMode()) {
mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
+ if (getWindowConfiguration().persistTaskBounds()) {
+ final Rect currentBounds = getRequestedOverrideBounds();
+ if (!currentBounds.isEmpty()) {
+ setLastNonFullscreenBounds(currentBounds);
+ }
+ }
// TODO: Should also take care of Pip mode changes here.
saveLaunchingStateIfNeeded();
@@ -1869,6 +1867,45 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
}
/**
+ * Adjust bounds to stay within stack bounds.
+ *
+ * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
+ * that keep them unchanged, but be contained within the stack bounds.
+ *
+ * @param bounds Bounds to be adjusted.
+ * @param stackBounds Bounds within which the other bounds should remain.
+ */
+ private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
+ if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
+ return;
+ }
+
+ if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
+ final int maxRight = stackBounds.right
+ - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
+ int horizontalDiff = stackBounds.left - bounds.left;
+ if ((horizontalDiff < 0 && bounds.left >= maxRight)
+ || (bounds.left + horizontalDiff >= maxRight)) {
+ horizontalDiff = maxRight - bounds.left;
+ }
+ bounds.left += horizontalDiff;
+ bounds.right += horizontalDiff;
+ }
+
+ if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
+ final int maxBottom = stackBounds.bottom
+ - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
+ int verticalDiff = stackBounds.top - bounds.top;
+ if ((verticalDiff < 0 && bounds.top >= maxBottom)
+ || (bounds.top + verticalDiff >= maxBottom)) {
+ verticalDiff = maxBottom - bounds.top;
+ }
+ bounds.top += verticalDiff;
+ bounds.bottom += verticalDiff;
+ }
+ }
+
+ /**
* Displayed bounds are used to set where the task is drawn at any given time. This is
* separate from its actual bounds so that the app doesn't see any meaningful configuration
* changes during transitionary periods.
@@ -1879,9 +1916,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
} else {
mDisplayedBounds.set(bounds);
}
- final TaskWindowContainerController controller = getWindowContainerController();
- if (controller != null && controller.mContainer != null) {
- controller.mContainer.setOverrideDisplayedBounds(
+ if (mTask != null) {
+ mTask.setOverrideDisplayedBounds(
mDisplayedBounds.isEmpty() ? null : mDisplayedBounds);
}
}
@@ -1901,46 +1937,205 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont
return !mDisplayedBounds.isEmpty();
}
- /** Clears passed config and fills it with new override values. */
- // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't
- // depend on task or stacks, but uses those object to get the display to base the calculation
- // on. Probably best to centralize calculations like this in ConfigurationContainer.
- void computeOverrideConfiguration(Configuration config, Rect bounds,
- boolean overrideWidth, boolean overrideHeight) {
- mTmpNonDecorBounds.set(bounds);
- mTmpStableBounds.set(bounds);
+ /**
+ * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
+ * intersectBounds on a side, then the respective side will not be intersected.
+ *
+ * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
+ * inset on that side is no-longer applicable. This scenario happens when a task's minimal
+ * bounds are larger than the provided parent/display bounds.
+ *
+ * @param inOutBounds the bounds to intersect.
+ * @param intersectBounds the bounds to intersect with.
+ * @param intersectInsets insets to apply to intersectBounds before intersecting.
+ */
+ private static void intersectWithInsetsIfFits(
+ Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
+ if (inOutBounds.right <= intersectBounds.right) {
+ inOutBounds.right =
+ Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
+ }
+ if (inOutBounds.bottom <= intersectBounds.bottom) {
+ inOutBounds.bottom =
+ Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
+ }
+ if (inOutBounds.left >= intersectBounds.left) {
+ inOutBounds.left =
+ Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
+ }
+ if (inOutBounds.top >= intersectBounds.top) {
+ inOutBounds.top =
+ Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
+ }
+ }
+
+ /**
+ * Gets bounds with non-decor and stable insets applied respectively.
+ *
+ * If bounds overhangs the display, those edges will not get insets. See
+ * {@link #intersectWithInsetsIfFits}
+ *
+ * @param outNonDecorBounds where to place bounds with non-decor insets applied.
+ * @param outStableBounds where to place bounds with stable insets applied.
+ * @param bounds the bounds to inset.
+ */
+ private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
+ DisplayInfo displayInfo) {
+ outNonDecorBounds.set(bounds);
+ outStableBounds.set(bounds);
+ if (getStack() == null || getStack().getDisplay() == null) {
+ return;
+ }
+ DisplayPolicy policy = getStack().getDisplay().mDisplayContent.getDisplayPolicy();
+ if (policy == null) {
+ return;
+ }
+ mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+
+ policy.getStableInsetsLw(displayInfo.rotation,
+ displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout,
+ mTmpInsets);
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
- config.unset();
- final Configuration parentConfig = getParent().getConfiguration();
+ policy.getNonDecorInsetsLw(displayInfo.rotation,
+ displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout,
+ mTmpInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
+ }
- final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ /**
+ * Asks docked-divider controller for the smallestwidthdp given bounds.
+ * @param bounds bounds to calculate smallestwidthdp for.
+ */
+ private int getSmallestScreenWidthDpForDockedBounds(Rect bounds) {
+ DisplayContent dc = mStack.getDisplay().mDisplayContent;
+ if (dc != null) {
+ return dc.getDockedDividerController().getSmallestWidthDpForBounds(bounds);
+ }
+ return Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ }
- if (mStack != null) {
- final StackWindowController stackController = mStack.getWindowContainerController();
- stackController.adjustConfigurationForBounds(bounds,
- mTmpNonDecorBounds, mTmpStableBounds, overrideWidth, overrideHeight, density,
- config, parentConfig, getWindowingMode());
- } else {
- throw new IllegalArgumentException("Expected stack when calculating override config");
- }
-
- config.orientation = (config.screenWidthDp <= config.screenHeightDp)
- ? Configuration.ORIENTATION_PORTRAIT
- : Configuration.ORIENTATION_LANDSCAPE;
-
- // For calculating screen layout, we need to use the non-decor inset screen area for the
- // calculation for compatibility reasons, i.e. screen area without system bars that could
- // never go away in Honeycomb.
- final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
- final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
- // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start override
- // calculation with partial default.
- // Reducing the screen layout starting from its parent config.
- final int sl = parentConfig.screenLayout &
- (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
- final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
- final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
- config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
+ /**
+ * Calculates configuration values used by the client to get resources. This should be run
+ * using app-facing bounds (bounds unmodified by animations or transient interactions).
+ *
+ * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
+ * configuring an "inherit-bounds" window which means that all configuration settings would
+ * just be inherited from the parent configuration.
+ **/
+ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds,
+ @NonNull Configuration parentConfig) {
+ int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+ }
+
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = parentConfig.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(bounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ }
+ if (windowingMode != WINDOWING_MODE_FREEFORM) {
+ final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ if (parentAppBounds != null && !parentAppBounds.isEmpty()) {
+ outAppBounds.intersect(parentAppBounds);
+ }
+ }
+
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
+ || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ if (mStack != null) {
+ final DisplayInfo di = new DisplayInfo();
+ mStack.getDisplay().mDisplay.getDisplayInfo(di);
+
+ // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
+ // area, i.e. the screen area without the system bars.
+ // The non decor inset are areas that could never be removed in Honeycomb. See
+ // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, bounds, di);
+ } else {
+ mTmpNonDecorBounds.set(bounds);
+ mTmpStableBounds.set(bounds);
+ }
+
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ inOutConfig.screenWidthDp = Math.min((int) (mTmpStableBounds.width() / density),
+ parentConfig.screenWidthDp);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ inOutConfig.screenHeightDp = Math.min((int) (mTmpStableBounds.height() / density),
+ parentConfig.screenHeightDp);
+ }
+
+ if (inOutConfig.smallestScreenWidthDp
+ == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ if (WindowConfiguration.isFloating(windowingMode)) {
+ // For floating tasks, calculate the smallest width from the bounds of the task
+ inOutConfig.smallestScreenWidthDp = (int) (
+ Math.min(bounds.width(), bounds.height()) / density);
+ } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
+ // Iterating across all screen orientations, and return the minimum of the task
+ // width taking into account that the bounds might change because the snap
+ // algorithm snaps to a different value
+ getSmallestScreenWidthDpForDockedBounds(bounds);
+ }
+ // otherwise, it will just inherit
+ }
+ }
+
+ if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+ }
+ if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
+ // For calculating screen layout, we need to use the non-decor inset screen area for the
+ // calculation for compatibility reasons, i.e. screen area without system bars that
+ // could never go away in Honeycomb.
+ final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+ final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+ // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start
+ // override calculation with partial default.
+ // Reducing the screen layout starting from its parent config.
+ final int sl = parentConfig.screenLayout
+ & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+ final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
+ final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
+ inOutConfig.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
+ }
+ }
+
+ // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore.
+ void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig,
+ Configuration overrideConfig) {
+ inOutConfig.setTo(overrideConfig);
+
+ Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds();
+ if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) {
+ adjustForMinimalTaskDimensions(outOverrideBounds);
+
+ int windowingMode = overrideConfig.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ // by policy, make sure the window remains within parent
+ fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds());
+ }
+
+ computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig);
+ }
+ }
+
+ @Override
+ void resolveOverrideConfiguration(Configuration newParentConfig) {
+ computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig,
+ getRequestedOverrideConfiguration());
}
Rect updateOverrideConfigurationFromLaunchBounds() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 7ab4d086b70a..01a5622c2b60 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -191,9 +191,7 @@ class TaskSnapshotController {
} else {
mCache.putSnapshot(task, snapshot);
mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- if (task.getController() != null) {
- task.getController().reportSnapshotChanged(snapshot);
- }
+ task.onSnapshotChanged(snapshot);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
deleted file mode 100644
index b87b65e432d3..000000000000
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static com.android.server.EventLogTags.WM_TASK_CREATED;
-import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.EventLog;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Controller for the task container. This is created by activity manager to link task records to
- * the task container they use in window manager.
- *
- * Test class: {@link TaskWindowContainerControllerTests}
- */
-public class TaskWindowContainerController
- extends WindowContainerController<Task, TaskWindowContainerListener> {
-
- private final int mTaskId;
- private final H mHandler;
-
- public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
- StackWindowController stackController, int userId, Rect bounds, int resizeMode,
- boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
- TaskDescription taskDescription) {
- this(taskId, listener, stackController, userId, bounds, resizeMode,
- supportsPictureInPicture, toTop, showForAllUsers, taskDescription,
- WindowManagerService.getInstance());
- }
-
- public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
- StackWindowController stackController, int userId, Rect bounds, int resizeMode,
- boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
- TaskDescription taskDescription, WindowManagerService service) {
- super(listener, service);
- mTaskId = taskId;
- mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
-
- synchronized (mGlobalLock) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
- + " stack=" + stackController + " bounds=" + bounds);
-
- final TaskStack stack = stackController.mContainer;
- if (stack == null) {
- throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
- + stackController);
- }
- EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
- final Task task = createTask(taskId, stack, userId, resizeMode,
- supportsPictureInPicture, taskDescription);
- final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
- // We only want to move the parents to the parents if we are creating this task at the
- // top of its stack.
- stack.addTask(task, position, showForAllUsers, toTop /* moveParents */);
- }
- }
-
- @VisibleForTesting
- Task createTask(int taskId, TaskStack stack, int userId, int resizeMode,
- boolean supportsPictureInPicture, TaskDescription taskDescription) {
- return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture,
- taskDescription, this);
- }
-
- @Override
- public void removeContainer() {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + mTaskId);
- return;
- }
- mContainer.removeIfPossible();
- super.removeContainer();
- }
- }
-
- void positionChildAtTop(AppWindowToken aToken) {
- positionChildAt(aToken, POSITION_TOP);
- }
-
- void positionChildAt(AppWindowToken aToken, int position) {
- synchronized (mService.mGlobalLock) {
- if (aToken == null) {
- Slog.w(TAG_WM,
- "Attempted to position of non-existing app");
- return;
- }
-
- final Task task = mContainer;
- if (task == null) {
- throw new IllegalArgumentException("positionChildAt: invalid task=" + this);
- }
- task.positionChildAt(position, aToken, false /* includeParents */);
- }
- }
-
- public void reparent(StackWindowController stackController, int position, boolean moveParents) {
- synchronized (mGlobalLock) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
- + " to stack=" + stackController + " at " + position);
- if (mContainer == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM,
- "reparent: could not find taskId=" + mTaskId);
- return;
- }
- final TaskStack stack = stackController.mContainer;
- if (stack == null) {
- throw new IllegalArgumentException("reparent: could not find stack="
- + stackController);
- }
- mContainer.reparent(stack, position, moveParents);
- mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
- }
-
- public void setResizeable(int resizeMode) {
- synchronized (mGlobalLock) {
- if (mContainer != null) {
- mContainer.setResizeable(resizeMode);
- }
- }
- }
-
- public void resize(boolean relayout, boolean forced) {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found.");
- }
-
- if (mContainer.setBounds(
- mContainer.getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE
- && relayout) {
- mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
- }
- }
-
- public void getBounds(Rect bounds) {
- synchronized (mGlobalLock) {
- if (mContainer != null) {
- mContainer.getBounds(bounds);
- return;
- }
- bounds.setEmpty();
- }
- }
-
- /**
- * Puts this task into docked drag resizing mode. See {@link DragResizeMode}.
- *
- * @param resizing Whether to put the task into drag resize mode.
- */
- public void setTaskDockedResizing(boolean resizing) {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + mTaskId + " not found.");
- return;
- }
- mContainer.setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
- }
- }
-
- public void cancelWindowTransition() {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- Slog.w(TAG_WM, "cancelWindowTransition: taskId " + mTaskId + " not found.");
- return;
- }
- mContainer.cancelTaskWindowTransition();
- }
- }
-
- public void setTaskDescription(TaskDescription taskDescription) {
- synchronized (mGlobalLock) {
- if (mContainer == null) {
- Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
- return;
- }
- mContainer.setTaskDescription(taskDescription);
- }
- }
-
- public boolean isDragResizing() {
- synchronized (mGlobalLock) {
- return mContainer.isDragResizing();
- }
- }
-
- void reportSnapshotChanged(TaskSnapshot snapshot) {
- mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
- }
-
- void requestResize(Rect bounds, int resizeMode) {
- mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget();
- }
-
- @Override
- public String toString() {
- return "{TaskWindowContainerController taskId=" + mTaskId + "}";
- }
-
- private static final class H extends Handler {
-
- static final int REPORT_SNAPSHOT_CHANGED = 0;
- static final int REQUEST_RESIZE = 1;
-
- private final WeakReference<TaskWindowContainerController> mController;
-
- H(WeakReference<TaskWindowContainerController> controller, Looper looper) {
- super(looper);
- mController = controller;
- }
-
- @Override
- public void handleMessage(Message msg) {
- final TaskWindowContainerController controller = mController.get();
- final TaskWindowContainerListener listener = (controller != null)
- ? controller.mListener : null;
- if (listener == null) {
- return;
- }
- switch (msg.what) {
- case REPORT_SNAPSHOT_CHANGED:
- listener.onSnapshotChanged((TaskSnapshot) msg.obj);
- break;
- case REQUEST_RESIZE:
- listener.requestResize((Rect) msg.obj, msg.arg1);
- break;
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
deleted file mode 100644
index af67de38e5b3..000000000000
--- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Rect;
-
-/**
- * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with
- * the task container.
- */
-public interface TaskWindowContainerListener extends WindowContainerListener {
-
- /** Called when the snapshot of this task has changed. */
- void onSnapshotChanged(TaskSnapshot snapshot);
-
- /** Called when the task container would like its controller to resize. */
- void requestResize(Rect bounds, int resizeMode);
-}
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 649f1a56f011..4d4a7b41643c 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -34,7 +34,6 @@
#include "netdbpf/BpfNetworkStats.h"
using android::bpf::Stats;
-using android::bpf::hasBpfSupport;
using android::bpf::bpfGetUidStats;
using android::bpf::bpfGetIfaceStats;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 240b8206baf6..d8225b38487c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -99,6 +99,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { }
@Override
+ public int getPasswordComplexity() {
+ return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+ }
+
+ @Override
public void installUpdateFromFile(ComponentName admin,
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
@@ -136,4 +141,10 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
public boolean isUnattendedManagedKiosk() {
return false;
}
+
+ @Override
+ public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId,
+ long start, long end, boolean allDay, int flags) {
+ return false;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bc550dc8bd12..7186cdf96dee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;
import static android.Manifest.permission.BIND_DEVICE_ADMIN;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
@@ -40,10 +41,13 @@ import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED;
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE;
import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_INSTALLATION;
import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
@@ -53,6 +57,7 @@ import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLE
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF;
@@ -113,6 +118,7 @@ import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
@@ -124,6 +130,7 @@ import android.app.admin.SystemUpdatePolicy;
import android.app.backup.IBackupManager;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -184,6 +191,7 @@ import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.UserManagerInternal.UserRestrictionsListener;
import android.os.storage.StorageManager;
+import android.provider.CalendarContract;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
import android.provider.Settings;
@@ -361,9 +369,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
DELEGATION_PACKAGE_ACCESS,
DELEGATION_PERMISSION_GRANT,
DELEGATION_INSTALL_EXISTING_PACKAGE,
- DELEGATION_KEEP_UNINSTALLED_PACKAGES
+ DELEGATION_KEEP_UNINSTALLED_PACKAGES,
+ DELEGATION_NETWORK_LOGGING,
+ DELEGATION_CERT_SELECTION,
+ DELEGATION_PACKAGE_INSTALLATION
};
+ // Subset of delegations that can only be delegated by Device Owner.
+ private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] {
+ DELEGATION_NETWORK_LOGGING,
+ DELEGATION_PACKAGE_INSTALLATION
+ });
+
+ // Subset of delegations that only one single package within a given user can hold
+ private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] {
+ DELEGATION_NETWORK_LOGGING,
+ DELEGATION_CERT_SELECTION,
+ });
+
/**
* System property whose value is either "true" or "false", indicating whether
* device owner is present.
@@ -4714,6 +4737,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
+ @PasswordComplexity
+ public int getPasswordComplexity() {
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ enforceUserUnlocked(callingUserId);
+ mContext.enforceCallingOrSelfPermission(
+ GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY,
+ "Must have " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY + " permission.");
+
+ synchronized (getLockObject()) {
+ int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false);
+ PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId);
+ return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity();
+ }
+ }
+
+ @Override
public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
enforceFullCrossUsersPermission(userHandle);
synchronized (getLockObject()) {
@@ -5346,7 +5385,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
if (who == null) {
- if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
+ if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+ DELEGATION_CERT_INSTALL)) {
mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
}
} else {
@@ -5765,13 +5805,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS);
- intent.setComponent(aliasChooser);
intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid);
intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI, uri);
intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias);
intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final ComponentName delegateReceiver;
+ delegateReceiver = resolveDelegateReceiver(DELEGATION_CERT_SELECTION,
+ DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS, caller.getIdentifier());
+
+ if (delegateReceiver != null) {
+ intent.setComponent(delegateReceiver);
+ } else {
+ intent.setComponent(aliasChooser);
+ }
+
final long id = mInjector.binderClearCallingIdentity();
try {
mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() {
@@ -5831,22 +5880,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
@Override
public void setDelegatedScopes(ComponentName who, String delegatePackage,
- List<String> scopes) throws SecurityException {
+ List<String> scopeList) throws SecurityException {
Preconditions.checkNotNull(who, "ComponentName is null");
Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
- Preconditions.checkCollectionElementsNotNull(scopes, "Scopes");
+ Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes");
// Remove possible duplicates.
- scopes = new ArrayList(new ArraySet(scopes));
+ final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList));
// Ensure given scopes are valid.
if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
throw new IllegalArgumentException("Unexpected delegation scopes");
}
-
+ final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
// Retrieve the user ID of the calling process.
final int userId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
// Ensure calling process is device/profile owner.
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (hasDoDelegation) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ } else {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ }
// Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
if (shouldCheckIfDelegatePackageIsInstalled(delegatePackage,
getTargetSdk(who.getPackageName(), userId), scopes)) {
@@ -5859,31 +5912,57 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// Set the new delegate in user policies.
final DevicePolicyData policy = getUserData(userId);
+ List<String> exclusiveScopes = null;
if (!scopes.isEmpty()) {
policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes));
+ exclusiveScopes = new ArrayList<>(scopes);
+ exclusiveScopes.retainAll(EXCLUSIVE_DELEGATIONS);
} else {
// Remove any delegation info if the given scopes list is empty.
policy.mDelegationMap.remove(delegatePackage);
}
-
- // Notify delegate package of updates.
- final Intent intent = new Intent(
- DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
- // Only call receivers registered with Context#registerReceiver (don’t wake delegate).
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- // Limit components this intent resolves to to the delegate package.
- intent.setPackage(delegatePackage);
- // Include the list of delegated scopes as an extra.
- intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES,
- (ArrayList<String>) scopes);
- // Send the broadcast.
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
-
+ sendDelegationChangedBroadcast(delegatePackage, scopes, userId);
+
+ // If set, remove exclusive scopes from all other delegates
+ if (exclusiveScopes != null && !exclusiveScopes.isEmpty()) {
+ for (Map.Entry<String, List<String>> entry : policy.mDelegationMap.entrySet()) {
+ final String currentPackage = entry.getKey();
+ final List<String> currentScopes = entry.getValue();
+
+ if (!currentPackage.equals(delegatePackage)) {
+ // Iterate through all other delegates
+ if (currentScopes.removeAll(exclusiveScopes)) {
+ // And if this delegate had some exclusive scopes which are now moved
+ // to the new delegate, notify about its delegation changes.
+ if (currentScopes.isEmpty()) {
+ policy.mDelegationMap.remove(currentPackage);
+ }
+ sendDelegationChangedBroadcast(currentPackage,
+ new ArrayList<>(currentScopes), userId);
+ }
+ }
+ }
+ }
// Persist updates.
saveSettingsLocked(userId);
}
}
+ private void sendDelegationChangedBroadcast(String delegatePackage, ArrayList<String> scopes,
+ int userId) {
+ // Notify delegate package of updates.
+ final Intent intent = new Intent(
+ DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
+ // Only call receivers registered with Context#registerReceiver (don’t wake delegate).
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ // Limit components this intent resolves to to the delegate package.
+ intent.setPackage(delegatePackage);
+ // Include the list of delegated scopes as an extra.
+ intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes);
+ // Send the broadcast.
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+
/**
* Get the delegation scopes given to a delegate package by a device owner or profile owner.
*
@@ -5951,17 +6030,59 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
// Ensure calling process is device/profile owner.
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- final DevicePolicyData policy = getUserData(userId);
+ return getDelegatePackagesInternalLocked(scope, userId);
+ }
+ }
- // Create a list to hold the resulting delegate packages.
- final List<String> delegatePackagesWithScope = new ArrayList<>();
- // Add all delegations containing scope to the result list.
- for (int i = 0; i < policy.mDelegationMap.size(); i++) {
- if (policy.mDelegationMap.valueAt(i).contains(scope)) {
- delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
- }
+ private List<String> getDelegatePackagesInternalLocked(String scope, int userId) {
+ final DevicePolicyData policy = getUserData(userId);
+
+ // Create a list to hold the resulting delegate packages.
+ final List<String> delegatePackagesWithScope = new ArrayList<>();
+ // Add all delegations containing scope to the result list.
+ for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+ if (policy.mDelegationMap.valueAt(i).contains(scope)) {
+ delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
}
- return delegatePackagesWithScope;
+ }
+ return delegatePackagesWithScope;
+ }
+
+ /**
+ * Return the ComponentName of the receiver that handles the given broadcast action, from
+ * the app that holds the given delegation capability. If the app defines multiple receivers
+ * with the same intent action filter, will return any one of them nondeterministically.
+ *
+ * @return ComponentName of the receiver or {@null} if none exists.
+ */
+ private ComponentName resolveDelegateReceiver(String scope, String action, int userId) {
+
+ final List<String> delegates;
+ synchronized (getLockObject()) {
+ delegates = getDelegatePackagesInternalLocked(scope, userId);
+ }
+ if (delegates.size() != 1) {
+ Slog.wtf(LOG_TAG, "More than one delegate holds " + scope);
+ return null;
+ }
+ final String pkg = delegates.get(0);
+ Intent intent = new Intent(action);
+ intent.setPackage(pkg);
+ final List<ResolveInfo> receivers;
+ try {
+ receivers = mIPackageManager.queryIntentReceivers(
+ intent, null, 0, userId).getList();
+ } catch (RemoteException e) {
+ return null;
+ }
+ final int count = receivers.size();
+ if (count >= 1) {
+ if (count > 1) {
+ Slog.w(LOG_TAG, pkg + " defines more than one delegate receiver for " + action);
+ }
+ return receivers.get(0).activityInfo.getComponentName();
+ } else {
+ return null;
}
}
@@ -5978,15 +6099,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
* @param scope the delegation scope to be checked.
* @return {@code true} if the calling process is a delegate of {@code scope}.
*/
- private boolean isCallerDelegate(String callerPackage, String scope) {
+ private boolean isCallerDelegate(String callerPackage, int callerUid, String scope) {
Preconditions.checkNotNull(callerPackage, "callerPackage is null");
if (!Arrays.asList(DELEGATIONS).contains(scope)) {
throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
}
// Retrieve the UID and user ID of the calling process.
- final int callingUid = mInjector.binderGetCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
+ final int userId = UserHandle.getUserId(callerUid);
synchronized (getLockObject()) {
// Retrieve user policy data.
final DevicePolicyData policy = getUserData(userId);
@@ -5999,7 +6119,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final int uid = mInjector.getPackageManager()
.getPackageUidAsUser(callerPackage, userId);
// Return true if the caller is actually callerPackage.
- return uid == callingUid;
+ return uid == callerUid;
} catch (NameNotFoundException e) {
// Ignore.
}
@@ -6024,15 +6144,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy,
String scope) {
+ enforceCanManageScopeOrCheckPermission(who, callerPackage, reqPolicy, scope, null);
+ }
+
+ /**
+ * Throw a security exception if a ComponentName is given and it is not a device/profile owner
+ * OR if the calling process is not a delegate of the given scope and does not hold the
+ * required permission.
+ */
+ private void enforceCanManageScopeOrCheckPermission(@Nullable ComponentName who,
+ @NonNull String callerPackage, int reqPolicy, @NonNull String scope,
+ @Nullable String permission) {
// If a ComponentName is given ensure it is a device or profile owner according to policy.
if (who != null) {
synchronized (getLockObject()) {
getActiveAdminForCallerLocked(who, reqPolicy);
}
- // If no ComponentName is given ensure calling process has scope delegation.
- } else if (!isCallerDelegate(callerPackage, scope)) {
- throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
- + " is not a delegate of scope " + scope + ".");
+ } else {
+ // If no ComponentName is given ensure calling process has scope delegation or required
+ // permission
+ if (isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), scope)) {
+ return;
+ }
+ if (permission == null) {
+ throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
+ + " is not a delegate of scope " + scope + ".");
+ } else {
+ mContext.enforceCallingOrSelfPermission(permission, null);
+ }
}
}
@@ -6971,9 +7110,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException {
+ private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who)
+ throws SecurityException {
synchronized (getLockObject()) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ ensureAllUsersAffiliated();
+ }
+
+ private void ensureAllUsersAffiliated() throws SecurityException {
+ synchronized (getLockObject()) {
if (!areAllUsersAffiliatedWithDeviceLocked()) {
throw new SecurityException("Not all users are affiliated.");
}
@@ -7032,14 +7178,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
void sendDeviceOwnerCommand(String action, Bundle extras) {
- int deviceOwnerUserId;
- ComponentName deviceOwnerComponent;
+ final int deviceOwnerUserId;
synchronized (getLockObject()) {
deviceOwnerUserId = mOwners.getDeviceOwnerUserId();
- deviceOwnerComponent = mOwners.getDeviceOwnerComponent();
}
- sendActiveAdminCommand(action, extras, deviceOwnerUserId,
- deviceOwnerComponent);
+
+ ComponentName receiverComponent = null;
+ if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) {
+ receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action,
+ deviceOwnerUserId);
+ }
+ if (receiverComponent == null) {
+ synchronized (getLockObject()) {
+ receiverComponent = mOwners.getDeviceOwnerComponent();
+ }
+ }
+ sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
}
private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) {
@@ -8496,7 +8650,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
- return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
+ return isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+ DELEGATION_APP_RESTRICTIONS);
}
@Override
@@ -10715,6 +10870,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
+ public boolean canSilentlyInstallPackage(String callerPackage, int callerUid) {
+ if (callerPackage == null) {
+ return false;
+ }
+ if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid))
+ && isActiveAdminWithPolicy(callerUid,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)) {
+ // device owner or a profile owner affiliated with the device owner
+ return true;
+ }
+ if (DevicePolicyManagerService.this.isCallerDelegate(callerPackage, callerUid,
+ DELEGATION_PACKAGE_INSTALLATION)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void reportSeparateProfileChallengeChanged(@UserIdInt int userId) {
synchronized (getLockObject()) {
updateMaximumTimeToLockLocked(userId);
@@ -12507,13 +12680,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) {
+ public void setNetworkLoggingEnabled(@Nullable ComponentName admin,
+ @NonNull String packageName, boolean enabled) {
if (!mHasFeature) {
return;
}
synchronized (getLockObject()) {
- Preconditions.checkNotNull(admin);
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+ DELEGATION_NETWORK_LOGGING);
if (enabled == isNetworkLoggingEnabledInternalLocked()) {
// already in the requested state
@@ -12614,12 +12788,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public boolean isNetworkLoggingEnabled(ComponentName admin) {
+ public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin,
+ @NonNull String packageName) {
if (!mHasFeature) {
return false;
}
synchronized (getLockObject()) {
- enforceDeviceOwnerOrManageUsers();
+ enforceCanManageScopeOrCheckPermission(admin, packageName,
+ DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, DELEGATION_NETWORK_LOGGING,
+ android.Manifest.permission.MANAGE_USERS);
return isNetworkLoggingEnabledInternalLocked();
}
}
@@ -12637,12 +12814,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
* @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH
*/
@Override
- public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) {
+ public List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin,
+ @NonNull String packageName, long batchToken) {
if (!mHasFeature) {
return null;
}
- Preconditions.checkNotNull(admin);
- ensureDeviceOwnerAndAllUsersAffiliated(admin);
+ enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+ DELEGATION_NETWORK_LOGGING);
+ ensureAllUsersAffiliated();
synchronized (getLockObject()) {
if (mNetworkLogger == null
@@ -13658,4 +13837,59 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private PowerManagerInternal getPowerManagerInternal() {
return mInjector.getPowerManagerInternal();
}
+
+ @Override
+ public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId,
+ long start, long end, boolean allDay, int flags) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name is empty");
+
+ final int callingUid = mInjector.binderGetCallingUid();
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ if (!isCallingFromPackage(packageName, callingUid)) {
+ throw new SecurityException("Input package name doesn't align with actual "
+ + "calling package.");
+ }
+ final long identity = mInjector.binderClearCallingIdentity();
+ try {
+ final int workProfileUserId = getManagedUserId(callingUserId);
+ if (workProfileUserId < 0) {
+ return false;
+ }
+ if (!isPackageAllowedToAccessCalendarForUser(packageName, workProfileUserId)) {
+ Log.d(LOG_TAG, String.format("Package %s is not allowed to access cross-profile"
+ + "calendar APIs", packageName));
+ return false;
+ }
+ final Intent intent = new Intent(CalendarContract.ACTION_VIEW_WORK_CALENDAR_EVENT);
+ intent.setPackage(packageName);
+ intent.putExtra(CalendarContract.EXTRA_EVENT_ID, eventId);
+ intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start);
+ intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
+ intent.putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, allDay);
+ intent.setFlags(flags);
+ try {
+ mContext.startActivityAsUser(intent, UserHandle.of(workProfileUserId));
+ } catch (ActivityNotFoundException e) {
+ Log.e(LOG_TAG, "View event activity not found", e);
+ return false;
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(identity);
+ }
+ return true;
+ }
+
+ private boolean isCallingFromPackage(String packageName, int callingUid) {
+ try {
+ final int packageUid = mInjector.getPackageManager().getPackageUidAsUser(
+ packageName, UserHandle.getUserId(callingUid));
+ return packageUid == callingUid;
+ } catch (NameNotFoundException e) {
+ Log.d(LOG_TAG, "Calling package not found", e);
+ return false;
+ }
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
index 7910598d8429..d8a875d7747b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.StartInstallingUpdateCallback;
import android.content.Context;
@@ -26,6 +27,7 @@ import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.stats.devicepolicy.DevicePolicyEnums;
import android.util.Log;
import java.io.File;
@@ -132,6 +134,10 @@ abstract class UpdateInstaller {
protected void notifyCallbackOnError(int errorCode, String errorMessage) {
cleanupUpdateFile();
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE_ERROR)
+ .setInt(errorCode)
+ .write();
try {
mCallback.onStartInstallingUpdateError(errorCode, errorMessage);
} catch (RemoteException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 88f645defa6d..e1b83fc99897 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -109,6 +109,7 @@ import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
+import com.android.server.pm.DynamicCodeLoggingService;
import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.OtaDexoptService;
@@ -1667,6 +1668,18 @@ public final class SystemServer {
traceEnd();
if (!isWatch) {
+ // We don't run this on watches as there are no plans to use the data logged
+ // on watch devices.
+ traceBeginAndSlog("StartDynamicCodeLoggingService");
+ try {
+ DynamicCodeLoggingService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("starting DynamicCodeLoggingService", e);
+ }
+ traceEnd();
+ }
+
+ if (!isWatch) {
traceBeginAndSlog("StartPruneInstantAppsJobService");
try {
PruneInstantAppsJobService.schedule(context);
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index a7209a076461..b9cc372c5138 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -111,7 +111,7 @@ public class ApfFilter {
* the last writable 32bit word.
*/
@VisibleForTesting
- private static enum Counter {
+ public static enum Counter {
RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds)
TOTAL_PACKETS,
PASSED_ARP,
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
new file mode 100644
index 000000000000..8e78a5686b85
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+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_VALIDATED;
+
+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;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkPolicyManager;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.DataUnit;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.net.NetworkPolicyManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConnectivityControllerTest {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private ConnectivityManager mConnManager;
+ @Mock
+ private NetworkPolicyManager mNetPolicyManager;
+ @Mock
+ private NetworkPolicyManagerInternal mNetPolicyManagerInternal;
+ @Mock
+ private JobSchedulerService mService;
+
+ private Constants mConstants;
+
+ private static final int UID_RED = 10001;
+ private static final int UID_BLUE = 10002;
+
+ @Before
+ public void setUp() throws Exception {
+ // Assume all packages are current SDK
+ final PackageManagerInternal pm = mock(PackageManagerInternal.class);
+ when(pm.getPackageTargetSdkVersion(anyString()))
+ .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, pm);
+
+ LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
+ LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
+
+ // Freeze the clocks at this moment in time
+ JobSchedulerService.sSystemClock =
+ Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sUptimeMillisClock =
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+
+ // Assume default constants for now
+ mConstants = new Constants();
+
+ // Get our mocks ready
+ when(mContext.getSystemServiceName(ConnectivityManager.class))
+ .thenReturn(Context.CONNECTIVITY_SERVICE);
+ when(mContext.getSystemService(ConnectivityManager.class))
+ .thenReturn(mConnManager);
+ when(mContext.getSystemServiceName(NetworkPolicyManager.class))
+ .thenReturn(Context.NETWORK_POLICY_SERVICE);
+ when(mContext.getSystemService(NetworkPolicyManager.class))
+ .thenReturn(mNetPolicyManager);
+ when(mService.getTestableContext()).thenReturn(mContext);
+ when(mService.getLock()).thenReturn(mService);
+ when(mService.getConstants()).thenReturn(mConstants);
+ }
+
+ @Test
+ public void testInsane() throws Exception {
+ final Network net = new Network(101);
+ final JobInfo.Builder job = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+
+ // Slow network is too slow
+ assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
+ createCapabilities().setLinkUpstreamBandwidthKbps(1)
+ .setLinkDownstreamBandwidthKbps(1), mConstants));
+ // Fast network looks great
+ assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net,
+ createCapabilities().setLinkUpstreamBandwidthKbps(1024)
+ .setLinkDownstreamBandwidthKbps(1024), mConstants));
+ }
+
+ @Test
+ public void testCongestion() throws Exception {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final JobInfo.Builder job = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
+ final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
+
+ // Uncongested network is whenever
+ {
+ final Network net = new Network(101);
+ final NetworkCapabilities caps = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED);
+ assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+ assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+ }
+
+ // Congested network is more selective
+ {
+ final Network net = new Network(101);
+ final NetworkCapabilities caps = createCapabilities();
+ assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+ assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+ }
+ }
+
+ @Test
+ public void testRelaxed() throws Exception {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final JobInfo.Builder job = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
+ final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
+
+ job.setIsPrefetch(true);
+ final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
+ final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);
+
+ // Unmetered network is whenever
+ {
+ final Network net = new Network(101);
+ final NetworkCapabilities caps = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_METERED);
+ assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+ assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+ assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
+ assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
+ }
+
+ // Metered network is only when prefetching and late
+ {
+ final Network net = new Network(101);
+ final NetworkCapabilities caps = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED);
+ assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+ assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+ assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
+ assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
+ }
+ }
+
+ @Test
+ public void testUpdates() throws Exception {
+ final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor
+ .forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture());
+
+ final ConnectivityController controller = new ConnectivityController(mService);
+
+ final Network meteredNet = new Network(101);
+ final NetworkCapabilities meteredCaps = createCapabilities();
+ final Network unmeteredNet = new Network(202);
+ final NetworkCapabilities unmeteredCaps = createCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_METERED);
+
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus blue = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+ // Pretend we're offline when job is added
+ {
+ reset(mConnManager);
+ answerNetwork(UID_RED, null, null);
+ answerNetwork(UID_BLUE, null, null);
+
+ controller.maybeStartTrackingJobLocked(red, null);
+ controller.maybeStartTrackingJobLocked(blue, null);
+
+ assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ }
+
+ // Metered network
+ {
+ reset(mConnManager);
+ answerNetwork(UID_RED, meteredNet, meteredCaps);
+ answerNetwork(UID_BLUE, meteredNet, meteredCaps);
+
+ callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps);
+
+ assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ }
+
+ // Unmetered network background
+ {
+ reset(mConnManager);
+ answerNetwork(UID_RED, meteredNet, meteredCaps);
+ answerNetwork(UID_BLUE, meteredNet, meteredCaps);
+
+ callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
+
+ assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ }
+
+ // Lost metered network
+ {
+ reset(mConnManager);
+ answerNetwork(UID_RED, unmeteredNet, unmeteredCaps);
+ answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
+
+ callback.getValue().onLost(meteredNet);
+
+ assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ }
+
+ // Specific UID was blocked
+ {
+ reset(mConnManager);
+ answerNetwork(UID_RED, null, null);
+ answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
+
+ callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
+
+ assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ }
+ }
+
+ @Test
+ public void testRequestStandbyExceptionLocked() {
+ final ConnectivityController controller = new ConnectivityController(mService);
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus blue = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+ InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+
+ controller.requestStandbyExceptionLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_RED), eq(true));
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ // Whitelisting doesn't need to be requested again.
+ controller.requestStandbyExceptionLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+
+ controller.requestStandbyExceptionLocked(blue);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_BLUE), eq(true));
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ }
+
+ @Test
+ public void testWouldBeReadyWithConnectivityLocked() {
+ final ConnectivityController controller = spy(new ConnectivityController(mService));
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+
+ doReturn(false).when(controller).isNetworkAvailable(any());
+ assertFalse(controller.wouldBeReadyWithConnectivityLocked(red));
+
+ doReturn(true).when(controller).isNetworkAvailable(any());
+ doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(any(),
+ eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(controller.wouldBeReadyWithConnectivityLocked(red));
+
+ doReturn(true).when(controller).isNetworkAvailable(any());
+ doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(any(),
+ eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(controller.wouldBeReadyWithConnectivityLocked(red));
+ }
+
+ @Test
+ public void testEvaluateStateLocked_HeartbeatsOn() {
+ mConstants.USE_HEARTBEATS = true;
+ final ConnectivityController controller = new ConnectivityController(mService);
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+
+ controller.evaluateStateLocked(red);
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ }
+
+ @Test
+ public void testEvaluateStateLocked_JobWithoutConnectivity() {
+ mConstants.USE_HEARTBEATS = false;
+ final ConnectivityController controller = new ConnectivityController(mService);
+ final JobStatus red = createJobStatus(createJob().setMinimumLatency(1));
+
+ controller.evaluateStateLocked(red);
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ }
+
+ @Test
+ public void testEvaluateStateLocked_JobWouldBeReady() {
+ mConstants.USE_HEARTBEATS = false;
+ final ConnectivityController controller = spy(new ConnectivityController(mService));
+ doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus blue = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+ InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+
+ controller.evaluateStateLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_RED), eq(true));
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ // Whitelisting doesn't need to be requested again.
+ controller.evaluateStateLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+
+ controller.evaluateStateLocked(blue);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_BLUE), eq(true));
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ }
+
+ @Test
+ public void testEvaluateStateLocked_JobWouldNotBeReady() {
+ mConstants.USE_HEARTBEATS = false;
+ final ConnectivityController controller = spy(new ConnectivityController(mService));
+ doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus blue = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+ InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+
+ controller.evaluateStateLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+
+ // Test that a currently whitelisted uid is now removed.
+ controller.requestStandbyExceptionLocked(blue);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_BLUE), eq(true));
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ controller.evaluateStateLocked(blue);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_BLUE), eq(false));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ }
+
+ @Test
+ public void testReevaluateStateLocked() {
+ mConstants.USE_HEARTBEATS = false;
+ final ConnectivityController controller = spy(new ConnectivityController(mService));
+ final JobStatus redOne = createJobStatus(createJob(1)
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus redTwo = createJobStatus(createJob(2)
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus blue = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+ controller.maybeStartTrackingJobLocked(redOne, null);
+ controller.maybeStartTrackingJobLocked(redTwo, null);
+ controller.maybeStartTrackingJobLocked(blue, null);
+
+ InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+ controller.requestStandbyExceptionLocked(redOne);
+ controller.requestStandbyExceptionLocked(redTwo);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_RED), eq(true));
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+
+ // Make sure nothing happens if an exception hasn't been requested.
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+ controller.reevaluateStateLocked(UID_BLUE);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+
+ // Make sure a job that isn't being tracked doesn't cause issues.
+ assertFalse(controller.isStandbyExceptionRequestedLocked(12345));
+ controller.reevaluateStateLocked(12345);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(12345), anyBoolean());
+
+ // Both jobs would still be ready. Exception should not be revoked.
+ doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
+ controller.reevaluateStateLocked(UID_RED);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+
+ // One job is still ready. Exception should not be revoked.
+ doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(eq(redOne));
+ doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(eq(redTwo));
+ controller.reevaluateStateLocked(UID_RED);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+
+ // Both jobs are not ready. Exception should be revoked.
+ doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
+ controller.reevaluateStateLocked(UID_RED);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_RED), eq(false));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ }
+
+ @Test
+ public void testMaybeRevokeStandbyExceptionLocked() {
+ final ConnectivityController controller = new ConnectivityController(mService);
+ final JobStatus red = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+ final JobStatus blue = createJobStatus(createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+ InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+ controller.requestStandbyExceptionLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_RED), eq(true));
+
+ // Try revoking for blue instead of red. Red should still have an exception requested.
+ controller.maybeRevokeStandbyExceptionLocked(blue);
+ inOrder.verify(mNetPolicyManagerInternal, never())
+ .setAppIdleWhitelist(anyInt(), anyBoolean());
+ assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+
+ // Now revoke for red.
+ controller.maybeRevokeStandbyExceptionLocked(red);
+ inOrder.verify(mNetPolicyManagerInternal, times(1))
+ .setAppIdleWhitelist(eq(UID_RED), eq(false));
+ assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+ }
+
+ private void answerNetwork(int uid, Network net, NetworkCapabilities caps) {
+ when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net);
+ when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps);
+ if (net != null) {
+ final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null);
+ ni.setDetailedState(DetailedState.CONNECTED, null, null);
+ when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni);
+ }
+ }
+
+ private static NetworkCapabilities createCapabilities() {
+ return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_VALIDATED);
+ }
+
+ private static JobInfo.Builder createJob() {
+ return createJob(101);
+ }
+
+ private static JobInfo.Builder createJob(int jobId) {
+ return new JobInfo.Builder(jobId, new ComponentName("foo", "bar"));
+ }
+
+ private static JobStatus createJobStatus(JobInfo.Builder job) {
+ return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE);
+ }
+
+ private static JobStatus createJobStatus(JobInfo.Builder job, int uid) {
+ return createJobStatus(job, uid, 0, Long.MAX_VALUE);
+ }
+
+ private static JobStatus createJobStatus(JobInfo.Builder job,
+ long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
+ return createJobStatus(job, android.os.Process.NOBODY_UID,
+ earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
+ }
+
+ private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
+ long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
+ return new JobStatus(job.build(), uid, null, -1, 0, 0, null,
+ earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index b2ec83583eba..71aec235640b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -87,7 +87,6 @@ public class QuotaControllerTest {
private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
private static final String TAG_CLEANUP = "*job.cleanup*";
private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
- private static final long IN_QUOTA_BUFFER_MILLIS = 30 * SECOND_IN_MILLIS;
private static final int CALLING_UID = 1000;
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
@@ -365,7 +364,8 @@ public class QuotaControllerTest {
final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
// Counting backwards, the quota will come back one minute before the end.
final long expectedAlarmTime =
- end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
+ end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
mQuotaController.saveTimingSession(0, "com.android.test",
new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -415,7 +415,8 @@ public class QuotaControllerTest {
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
- final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
+ final long expectedAlarmTime =
+ start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -468,7 +469,8 @@ public class QuotaControllerTest {
// Counting backwards, the first minute in the session is over the allowed time, so it
// needs to be excluded.
final long expectedAlarmTime =
- start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
+ start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -529,19 +531,22 @@ public class QuotaControllerTest {
// And down from there.
final long expectedWorkingAlarmTime =
- outOfQuotaTime + (2 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
+ outOfQuotaTime + (2 * HOUR_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
inOrder.verify(mAlarmManager, times(1))
.set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
final long expectedFrequentAlarmTime =
- outOfQuotaTime + (8 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
+ outOfQuotaTime + (8 * HOUR_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
inOrder.verify(mAlarmManager, times(1))
.set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
final long expectedRareAlarmTime =
- outOfQuotaTime + (24 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
+ outOfQuotaTime + (24 * HOUR_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
inOrder.verify(mAlarmManager, times(1))
.set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
@@ -775,6 +780,139 @@ public class QuotaControllerTest {
assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
+ @Test
+ public void testTimerTracking_AllBackground() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ List<TimingSession> expected = new ArrayList<>();
+
+ // Test single job.
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+ expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Test overlapping jobs.
+ JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
+
+ JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobStatus2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobStatus3);
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+ expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ /** Tests that Timers don't count foreground jobs. */
+ @Test
+ public void testTimerTracking_AllForeground() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
+ jobStatus.uidActive = true;
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ /**
+ * Tests that Timers properly track overlapping foreground and background jobs.
+ */
+ @Test
+ public void testTimerTracking_ForegroundAndBackground() {
+ setDischarging();
+
+ JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
+ JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
+ JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
+ jobFg3.uidActive = true;
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ // UID starts out inactive.
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Bg job starts while inactive, spans an entire active session, and ends after the
+ // active session.
+ // Fg job starts after the bg job and ends before the bg job.
+ // Entire bg job duration should be counted since it started before active session. However,
+ // count should only be 1 since Timer shouldn't count fg jobs.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobFg3);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+ expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
+ // "inactive" and then bg job 2 starts. Then fg job ends.
+ // This should result in two TimingSessions with a count of one each.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobFg3);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
+ advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+ expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
* its quota.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
new file mode 100644
index 000000000000..db69242538be
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.app.AlarmManager;
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.SystemClock;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.function.Predicate;
+
+@RunWith(AndroidJUnit4.class)
+public class StateControllerTest {
+ private static final long SECOND_IN_MILLIS = 1000L;
+ private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+ private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+ private static final int CALLING_UID = 1000;
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final int SOURCE_USER_ID = 0;
+
+ private Constants mConstants;
+ private StateController mStateController;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+
+ private class TestStateController extends StateController {
+ TestStateController(JobSchedulerService service) {
+ super(service);
+ }
+
+ public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+ }
+
+ public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+ boolean forUpdate) {
+ }
+
+ public void dumpControllerStateLocked(IndentingPrintWriter pw,
+ Predicate<JobStatus> predicate) {
+ }
+
+ public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+ Predicate<JobStatus> predicate) {
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+ // Use default constants for now.
+ mConstants = new Constants();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
+ // Called in QuotaController constructor.
+ // Used in JobStatus.
+ doReturn(mock(PackageManagerInternal.class))
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+
+ // Freeze the clocks at this moment in time
+ JobSchedulerService.sSystemClock =
+ Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sUptimeMillisClock =
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+
+ // Initialize real objects.
+ mStateController = new TestStateController(mJobSchedulerService);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private JobStatus createJobStatus(String testTag, int jobId) {
+ JobInfo jobInfo = new JobInfo.Builder(jobId,
+ new ComponentName(mContext, "TestQuotaJobService"))
+ .setMinimumLatency(Math.abs(jobId) + 1)
+ .build();
+ return JobStatus.createFromJobInfo(
+ jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ }
+
+ @Test
+ public void testWouldBeReadyWithConstraintLocked() {
+ JobStatus job = spy(createJobStatus("testWouldBeReadyWithConstraintLocked", 1));
+
+ when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(false);
+ assertFalse(mStateController.wouldBeReadyWithConstraintLocked(job, 1));
+
+ when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(true);
+ when(mJobSchedulerService.areComponentsInPlaceLocked(job)).thenReturn(false);
+ assertFalse(mStateController.wouldBeReadyWithConstraintLocked(job, 1));
+
+ when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(true);
+ when(mJobSchedulerService.areComponentsInPlaceLocked(job)).thenReturn(true);
+ assertTrue(mStateController.wouldBeReadyWithConstraintLocked(job, 1));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
index e6b328a128b7..ec5d93edc126 100644
--- a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
@@ -29,8 +29,7 @@ import android.content.pm.PackageManagerInternal;
import android.os.UserManagerInternal;
import android.os.storage.StorageManagerInternal;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.Zygote;
import org.junit.Before;
import org.junit.Test;
@@ -38,6 +37,9 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class StorageManagerServiceTest {
@@ -97,15 +99,15 @@ public class StorageManagerServiceTest {
when(mPm.getPackagesForUid(eq(UID_GREY))).thenReturn(new String[] { PKG_GREY });
when(mPm.getPackagesForUid(eq(UID_COLORS))).thenReturn(new String[] { PKG_RED, PKG_BLUE });
- setIsAppStorageSandboxed(PID_BLUE, UID_COLORS, true);
- setIsAppStorageSandboxed(PID_GREY, UID_GREY, true);
- setIsAppStorageSandboxed(PID_RED, UID_COLORS, true);
+ setStorageMountMode(PID_BLUE, UID_COLORS, Zygote.MOUNT_EXTERNAL_WRITE);
+ setStorageMountMode(PID_GREY, UID_GREY, Zygote.MOUNT_EXTERNAL_WRITE);
+ setStorageMountMode(PID_RED, UID_COLORS, Zygote.MOUNT_EXTERNAL_WRITE);
mService = new StorageManagerService(mContext);
}
- private void setIsAppStorageSandboxed(int pid, int uid, boolean sandboxed) {
- when(mAmi.isAppStorageSandboxed(pid, uid)).thenReturn(sandboxed);
+ private void setStorageMountMode(int pid, int uid, int mountMode) {
+ when(mAmi.getStorageMountMode(pid, uid)).thenReturn(mountMode);
}
@Test
@@ -210,7 +212,7 @@ public class StorageManagerServiceTest {
@Test
public void testPackageNotSandboxed() throws Exception {
- setIsAppStorageSandboxed(PID_RED, UID_COLORS, false);
+ setStorageMountMode(PID_RED, UID_COLORS, Zygote.MOUNT_EXTERNAL_FULL);
// Both app and system have the same view
assertTranslation(
@@ -224,6 +226,29 @@ public class StorageManagerServiceTest {
PID_RED, UID_COLORS);
}
+ @Test
+ public void testInstallerPackage() throws Exception {
+ setStorageMountMode(PID_GREY, UID_GREY, Zygote.MOUNT_EXTERNAL_INSTALLER);
+
+ assertTranslation(
+ "/storage/emulated/0/Android/obb/com.grey/foo.jpg",
+ "/storage/emulated/0/Android/obb/com.grey/foo.jpg",
+ PID_GREY, UID_GREY);
+ assertTranslation(
+ "/storage/emulated/0/Android/obb/com.blue/bar.jpg",
+ "/storage/emulated/0/Android/obb/com.blue/bar.jpg",
+ PID_GREY, UID_GREY);
+
+ assertTranslation(
+ "/storage/emulated/0/Android/data/com.grey/foo.jpg",
+ "/storage/emulated/0/Android/data/com.grey/foo.jpg",
+ PID_GREY, UID_GREY);
+ assertTranslation(
+ "/storage/emulated/0/Android/sandbox/com.grey/Android/data/com.blue/bar.jpg",
+ "/storage/emulated/0/Android/data/com.blue/bar.jpg",
+ PID_GREY, UID_GREY);
+ }
+
private void assertTranslation(String system, String sandbox,
int pid, int uid) throws Exception {
assertEquals(system,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c3a0ddaff85f..729fac5b1dff 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -21,6 +21,9 @@ import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
@@ -48,6 +51,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+import static org.testng.Assert.assertThrows;
import android.Manifest.permission;
import android.annotation.RawRes;
@@ -5133,6 +5137,71 @@ public class DevicePolicyManagerTest extends DpmTestBase {
});
}
+ public void testGetPasswordComplexity_securityExceptionIfParentInstance() {
+ assertThrows(SecurityException.class,
+ () -> new DevicePolicyManagerTestable(
+ mServiceContext,
+ dpms,
+ /* parentInstance= */ true)
+ .getPasswordComplexity());
+ }
+
+ public void testGetPasswordComplexity_illegalStateExceptionIfLocked() {
+ when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(false);
+ assertThrows(IllegalStateException.class, () -> dpm.getPasswordComplexity());
+ }
+
+ public void testGetPasswordComplexity_securityExceptionWithoutPermissions() {
+ when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(true);
+ assertThrows(SecurityException.class, () -> dpm.getPasswordComplexity());
+ }
+
+
+ public void testGetPasswordComplexity_currentUserNoPassword() {
+ when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(true);
+ mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
+
+ assertEquals(PASSWORD_COMPLEXITY_NONE, dpm.getPasswordComplexity());
+ }
+
+ public void testGetPasswordComplexity_currentUserHasPassword() {
+ when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(true);
+ mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+ when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
+ dpms.mUserPasswordMetrics.put(
+ DpmMockContext.CALLER_USER_HANDLE,
+ PasswordMetrics.computeForPassword("asdf"));
+
+ assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
+ }
+
+ public void testGetPasswordComplexity_unifiedChallengeReturnsParentUserPassword() {
+ when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(true);
+ mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+
+ UserInfo parentUser = new UserInfo();
+ parentUser.id = DpmMockContext.CALLER_USER_HANDLE + 10;
+ when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(parentUser.id);
+
+ dpms.mUserPasswordMetrics.put(
+ DpmMockContext.CALLER_USER_HANDLE,
+ PasswordMetrics.computeForPassword("asdf"));
+ dpms.mUserPasswordMetrics.put(
+ parentUser.id,
+ PasswordMetrics.computeForPassword("parentUser"));
+
+ assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
+ }
+
private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
index 3da61d69c742..4982d6e8817f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
@@ -26,7 +26,12 @@ public class DevicePolicyManagerTestable extends DevicePolicyManager {
public DevicePolicyManagerTestable(DpmMockContext context,
DevicePolicyManagerServiceTestable dpms) {
- super(context, dpms, /* parentInstance = */ false);
+ this(context, dpms, /* parentInstance= */ false);
+ }
+
+ public DevicePolicyManagerTestable(DpmMockContext context,
+ DevicePolicyManagerServiceTestable dpms, boolean parentInstance) {
+ super(context, dpms, parentInstance);
this.dpms = dpms;
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index b4212808d585..e9bfa8f4e0c8 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -18,11 +18,21 @@ package com.android.server.display;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.Curve;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayViewport;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.input.InputManagerInternal;
import android.os.Handler;
@@ -31,9 +41,9 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -49,17 +59,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Arrays;
import java.util.List;
-
-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 static org.junit.Assert.fail;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.mock;
+import java.util.stream.LongStream;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -397,6 +398,43 @@ public class DisplayManagerServiceTest {
displayManager.validateBrightnessConfiguration(null);
}
+ /**
+ * Tests that collection of display color sampling results are sensible.
+ */
+ @Test
+ public void testDisplayedContentSampling() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ registerDefaultDisplays(displayManager);
+
+ DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0);
+ assertNotNull(ddi);
+
+ DisplayedContentSamplingAttributes attr =
+ displayManager.getDisplayedContentSamplingAttributesInternal(0);
+ if (attr == null) return; //sampling not supported on device, skip remainder of test.
+
+ boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
+ assertTrue(!enabled);
+
+ displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
+ DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
+ assertNotNull(sample);
+
+ long numPixels = ddi.width * ddi.height * sample.getNumFrames();
+ long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0);
+ assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+ samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1);
+ assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+ samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2);
+ assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+ samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3);
+ assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+ }
+
private void registerDefaultDisplays(DisplayManagerService displayManager) {
Handler handler = displayManager.getDisplayHandler();
// Would prefer to call displayManager.onStart() directly here but it performs binderService
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
deleted file mode 100644
index 5b59e607cba7..000000000000
--- a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-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_VALIDATED;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.when;
-
-import android.app.job.JobInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManagerInternal;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkPolicyManager;
-import android.os.Build;
-import android.os.SystemClock;
-import android.util.DataUnit;
-
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobSchedulerService.Constants;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.time.Clock;
-import java.time.ZoneOffset;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ConnectivityControllerTest {
-
- @Mock private Context mContext;
- @Mock private ConnectivityManager mConnManager;
- @Mock private NetworkPolicyManager mNetPolicyManager;
- @Mock private JobSchedulerService mService;
-
- private Constants mConstants;
-
- private static final int UID_RED = 10001;
- private static final int UID_BLUE = 10002;
-
- @Before
- public void setUp() throws Exception {
- // Assume all packages are current SDK
- final PackageManagerInternal pm = mock(PackageManagerInternal.class);
- when(pm.getPackageTargetSdkVersion(anyString()))
- .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.addService(PackageManagerInternal.class, pm);
-
- // Freeze the clocks at this moment in time
- JobSchedulerService.sSystemClock =
- Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
- JobSchedulerService.sUptimeMillisClock =
- Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
- JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
-
- // Assume default constants for now
- mConstants = new Constants();
-
- // Get our mocks ready
- when(mContext.getSystemServiceName(ConnectivityManager.class))
- .thenReturn(Context.CONNECTIVITY_SERVICE);
- when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
- .thenReturn(mConnManager);
- when(mContext.getSystemServiceName(NetworkPolicyManager.class))
- .thenReturn(Context.NETWORK_POLICY_SERVICE);
- when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE))
- .thenReturn(mNetPolicyManager);
- when(mService.getTestableContext()).thenReturn(mContext);
- when(mService.getLock()).thenReturn(mService);
- when(mService.getConstants()).thenReturn(mConstants);
- }
-
- @Test
- public void testInsane() throws Exception {
- final Network net = new Network(101);
- final JobInfo.Builder job = createJob()
- .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
-
- // Slow network is too slow
- assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
- createCapabilities().setLinkUpstreamBandwidthKbps(1)
- .setLinkDownstreamBandwidthKbps(1), mConstants));
- // Fast network looks great
- assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net,
- createCapabilities().setLinkUpstreamBandwidthKbps(1024)
- .setLinkDownstreamBandwidthKbps(1024), mConstants));
- }
-
- @Test
- public void testCongestion() throws Exception {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- final JobInfo.Builder job = createJob()
- .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
- final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
- final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
-
- // Uncongested network is whenever
- {
- final Network net = new Network(101);
- final NetworkCapabilities caps = createCapabilities()
- .addCapability(NET_CAPABILITY_NOT_CONGESTED);
- assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
- assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
- }
-
- // Congested network is more selective
- {
- final Network net = new Network(101);
- final NetworkCapabilities caps = createCapabilities();
- assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
- assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
- }
- }
-
- @Test
- public void testRelaxed() throws Exception {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- final JobInfo.Builder job = createJob()
- .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
- final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
- final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
-
- job.setIsPrefetch(true);
- final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
- final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);
-
- // Unmetered network is whenever
- {
- final Network net = new Network(101);
- final NetworkCapabilities caps = createCapabilities()
- .addCapability(NET_CAPABILITY_NOT_CONGESTED)
- .addCapability(NET_CAPABILITY_NOT_METERED);
- assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
- assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
- assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
- assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
- }
-
- // Metered network is only when prefetching and late
- {
- final Network net = new Network(101);
- final NetworkCapabilities caps = createCapabilities()
- .addCapability(NET_CAPABILITY_NOT_CONGESTED);
- assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
- assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants));
- assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
- assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
- }
- }
-
- @Test
- public void testUpdates() throws Exception {
- final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor
- .forClass(NetworkCallback.class);
- doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture());
-
- final ConnectivityController controller = new ConnectivityController(mService);
-
- final Network meteredNet = new Network(101);
- final NetworkCapabilities meteredCaps = createCapabilities();
- final Network unmeteredNet = new Network(202);
- final NetworkCapabilities unmeteredCaps = createCapabilities()
- .addCapability(NET_CAPABILITY_NOT_METERED);
-
- final JobStatus red = createJobStatus(createJob()
- .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
- final JobStatus blue = createJobStatus(createJob()
- .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
-
- // Pretend we're offline when job is added
- {
- reset(mConnManager);
- answerNetwork(UID_RED, null, null);
- answerNetwork(UID_BLUE, null, null);
-
- controller.maybeStartTrackingJobLocked(red, null);
- controller.maybeStartTrackingJobLocked(blue, null);
-
- assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- }
-
- // Metered network
- {
- reset(mConnManager);
- answerNetwork(UID_RED, meteredNet, meteredCaps);
- answerNetwork(UID_BLUE, meteredNet, meteredCaps);
-
- callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps);
-
- assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- }
-
- // Unmetered network background
- {
- reset(mConnManager);
- answerNetwork(UID_RED, meteredNet, meteredCaps);
- answerNetwork(UID_BLUE, meteredNet, meteredCaps);
-
- callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
-
- assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- }
-
- // Lost metered network
- {
- reset(mConnManager);
- answerNetwork(UID_RED, unmeteredNet, unmeteredCaps);
- answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
-
- callback.getValue().onLost(meteredNet);
-
- assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- }
-
- // Specific UID was blocked
- {
- reset(mConnManager);
- answerNetwork(UID_RED, null, null);
- answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
-
- callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
-
- assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
- }
- }
-
- private void answerNetwork(int uid, Network net, NetworkCapabilities caps) {
- when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net);
- when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps);
- if (net != null) {
- final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null);
- ni.setDetailedState(DetailedState.CONNECTED, null, null);
- when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni);
- }
- }
-
- private static NetworkCapabilities createCapabilities() {
- return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_VALIDATED);
- }
-
- private static JobInfo.Builder createJob() {
- return new JobInfo.Builder(101, new ComponentName("foo", "bar"));
- }
-
- private static JobStatus createJobStatus(JobInfo.Builder job) {
- return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE);
- }
-
- private static JobStatus createJobStatus(JobInfo.Builder job, int uid) {
- return createJobStatus(job, uid, 0, Long.MAX_VALUE);
- }
-
- private static JobStatus createJobStatus(JobInfo.Builder job,
- long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
- return createJobStatus(job, android.os.Process.NOBODY_UID,
- earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
- }
-
- private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
- long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
- return new JobStatus(job.build(), uid, null, -1, 0, 0, null,
- earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index 87c3cd2dad06..3b6b48b6aa3f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -16,14 +16,20 @@
package com.android.server.pm.dex;
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
import android.os.storage.StorageManager;
import androidx.test.filters.SmallTest;
@@ -43,13 +49,12 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
-
-import java.util.Arrays;
+import org.mockito.stubbing.Stubber;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DexLoggerTests {
- private static final String PACKAGE_NAME = "package.name";
+ private static final String OWNING_PACKAGE_NAME = "package.name";
private static final String VOLUME_UUID = "volUuid";
private static final String DEX_PATH = "/bar/foo.jar";
private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE;
@@ -66,6 +71,7 @@ public class DexLoggerTests {
};
private static final String CONTENT_HASH =
"0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20";
+ private static final byte[] EMPTY_BYTES = {};
@Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@@ -73,92 +79,191 @@ public class DexLoggerTests {
@Mock Installer mInstaller;
private final Object mInstallLock = new Object();
- private DexManager.Listener mListener;
+ private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
+ private DexLogger mDexLogger;
private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create();
+ private boolean mWriteTriggered = false;
+ private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
+ DEX_FILENAME_HASH + " " + CONTENT_HASH;
@Before
- public void setup() {
+ public void setup() throws Exception {
+ // Disable actually attempting to do file writes.
+ mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() {
+ @Override
+ void maybeWriteAsync() {
+ mWriteTriggered = true;
+ }
+
+ @Override
+ protected void writeNow(Void data) {
+ throw new AssertionError("These tests should never call this method.");
+ }
+ };
+
// For test purposes capture log messages as well as sending to the event log.
- mListener = new DexLogger(mPM, mInstaller, mInstallLock) {
- @Override
+ mDexLogger = new DexLogger(mPM, mInstaller, mInstallLock, mPackageDynamicCodeLoading) {
+ @Override
void writeDclEvent(int uid, String message) {
super.writeDclEvent(uid, message);
mMessagesForUid.put(uid, message);
}
};
+
+ // Make the owning package exist in our mock PackageManager.
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.deviceProtectedDataDir = "/bar";
+ appInfo.uid = OWNER_UID;
+ appInfo.volumeUuid = VOLUME_UUID;
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = appInfo;
+
+ doReturn(packageInfo).when(mPM)
+ .getPackageInfo(OWNING_PACKAGE_NAME, /*flags*/ 0, OWNER_USER_ID);
}
@Test
- public void testSingleAppWithFileHash() throws Exception {
- doReturn(CONTENT_HASH_BYTES).when(mInstaller).hashSecondaryDexFile(
- DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+ public void testOneLoader_ownFile_withFileHash() throws Exception {
+ whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
- runOnReconcile();
+ recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
- assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID);
- String expectedMessage = DEX_FILENAME_HASH + " " + CONTENT_HASH;
- assertThat(mMessagesForUid).containsEntry(OWNER_UID, expectedMessage);
+ assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+
+ assertThat(mWriteTriggered).isFalse();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+ .containsExactly(OWNING_PACKAGE_NAME);
}
@Test
- public void testSingleAppNoFileHash() throws Exception {
- doReturn(new byte[] { }).when(mInstaller).hashSecondaryDexFile(
- DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+ public void testOneLoader_ownFile_noFileHash() throws Exception {
+ whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES));
- runOnReconcile();
+ recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
- assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID);
+ assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+
+ // File should be removed from the DCL list, since we can't hash it.
+ assertThat(mWriteTriggered).isTrue();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
}
@Test
- public void testSingleAppHashFails() throws Exception {
- doThrow(new InstallerException("Testing failure")).when(mInstaller).hashSecondaryDexFile(
- DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+ public void testOneLoader_ownFile_hashingFails() throws Exception {
+ whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test")));
- runOnReconcile();
+ recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+ assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+
+ // File should be removed from the DCL list, since we can't hash it.
+ assertThat(mWriteTriggered).isTrue();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
+ }
+
+ @Test
+ public void testOneLoader_ownFile_unknownPath() {
+ recordLoad(OWNING_PACKAGE_NAME, "other/path");
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid).isEmpty();
+ assertThat(mWriteTriggered).isTrue();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
}
@Test
- public void testOtherApps() throws Exception {
- doReturn(CONTENT_HASH_BYTES).when(mInstaller).hashSecondaryDexFile(
- DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+ public void testOneLoader_differentOwner() throws Exception {
+ whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ setPackageUid("other.package.name", 1001);
- // Simulate three packages from two different UIDs
- String packageName1 = "other1.package.name";
- String packageName2 = "other2.package.name";
- String packageName3 = "other3.package.name";
- int uid1 = 1001;
- int uid2 = 1002;
+ recordLoad("other.package.name", DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
- doReturn(uid1).when(mPM).getPackageUid(packageName1, 0, OWNER_USER_ID);
- doReturn(uid2).when(mPM).getPackageUid(packageName2, 0, OWNER_USER_ID);
- doReturn(uid1).when(mPM).getPackageUid(packageName3, 0, OWNER_USER_ID);
+ assertThat(mMessagesForUid.keys()).containsExactly(1001);
+ assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+ assertThat(mWriteTriggered).isFalse();
+ }
- runOnReconcile(packageName1, packageName2, packageName3);
+ @Test
+ public void testOneLoader_differentOwner_uninstalled() throws Exception {
+ whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ setPackageUid("other.package.name", -1);
- assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID, uid1, uid2);
+ recordLoad("other.package.name", DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
- String expectedMessage = DEX_FILENAME_HASH + " " + CONTENT_HASH;
- assertThat(mMessagesForUid).containsEntry(OWNER_UID, expectedMessage);
- assertThat(mMessagesForUid).containsEntry(uid1, expectedMessage);
- assertThat(mMessagesForUid).containsEntry(uid2, expectedMessage);
+ assertThat(mMessagesForUid).isEmpty();
+ assertThat(mWriteTriggered).isFalse();
}
- private void runOnReconcile(String... otherPackageNames) {
- ApplicationInfo appInfo = new ApplicationInfo();
- appInfo.packageName = PACKAGE_NAME;
- appInfo.volumeUuid = VOLUME_UUID;
- appInfo.uid = OWNER_UID;
+ @Test
+ public void testMultipleLoadersAndFiles() throws Exception {
+ String otherDexPath = "/bar/nosuchdir/foo.jar";
+ whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES));
+ setPackageUid("other.package.name1", 1001);
+ setPackageUid("other.package.name2", 1002);
+
+ recordLoad("other.package.name1", DEX_PATH);
+ recordLoad("other.package.name1", otherDexPath);
+ recordLoad("other.package.name2", DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+ assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID);
+ assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+ assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+
+ assertThat(mWriteTriggered).isTrue();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+ .containsExactly(OWNING_PACKAGE_NAME);
+
+ // Check the DexLogger caching is working
+ verify(mPM, atMost(1)).getPackageInfo(OWNING_PACKAGE_NAME, /*flags*/ 0, OWNER_USER_ID);
+ }
+
+ @Test
+ public void testUnknownOwner() {
+ reset(mPM);
+ recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ mDexLogger.logDynamicCodeLoading("other.package.name");
+
+ assertThat(mMessagesForUid).isEmpty();
+ assertThat(mWriteTriggered).isFalse();
+ verifyZeroInteractions(mPM);
+ }
+
+ @Test
+ public void testUninstalledPackage() {
+ reset(mPM);
+ recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
- boolean isUsedByOtherApps = otherPackageNames.length > 0;
- DexUseInfo dexUseInfo = new DexUseInfo(
- isUsedByOtherApps, OWNER_USER_ID, /* classLoaderContext */ null, /* loaderIsa */ null);
- dexUseInfo.getLoadingPackages().addAll(Arrays.asList(otherPackageNames));
+ assertThat(mMessagesForUid).isEmpty();
+ assertThat(mWriteTriggered).isTrue();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
+ }
+
+ private void setPackageUid(String packageName, int uid) throws Exception {
+ doReturn(uid).when(mPM).getPackageUid(packageName, /*flags*/ 0, OWNER_USER_ID);
+ }
+
+ private void whenFileIsHashed(String dexPath, Stubber stubber) throws Exception {
+ stubber.when(mInstaller).hashSecondaryDexFile(
+ dexPath, OWNING_PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+ }
- mListener.onReconcileSecondaryDexFile(appInfo, dexUseInfo, DEX_PATH, STORAGE_FLAGS);
+ private void recordLoad(String loadingPackageName, String dexPath) {
+ mPackageDynamicCodeLoading.record(
+ OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index fd07cb046fb5..7cd8ceddfd23 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -27,12 +27,6 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -78,7 +72,6 @@ public class DexManagerTests {
@Mock Installer mInstaller;
@Mock IPackageManager mPM;
private final Object mInstallLock = new Object();
- @Mock DexManager.Listener mListener;
private DexManager mDexManager;
@@ -114,9 +107,8 @@ public class DexManagerTests {
mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
DELEGATE_LAST_CLASS_LOADER_NAME);
- mDexManager = new DexManager(
- /*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock,
- mListener);
+ mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null,
+ mInstaller, mInstallLock);
// Foo and Bar are available to user0.
// Only Bar is available to user1;
@@ -415,9 +407,10 @@ public class DexManagerTests {
String frameworkDex = "/system/framework/com.android.location.provider.jar";
// Load a dex file from framework.
notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0);
- // The dex file should not be recognized as a package.
- assertFalse(mDexManager.hasInfoOnPackage(frameworkDex));
- assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex));
+ // The dex file should not be recognized as owned by the package.
+ assertFalse(mDexManager.hasInfoOnPackage(mFooUser0.getPackageName()));
+
+ assertNull(getPackageDynamicCodeInfo(mFooUser0));
}
@Test
@@ -510,21 +503,6 @@ public class DexManagerTests {
assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
}
- @Test
- public void testReconcileSecondaryDexFiles_invokesListener() throws Exception {
- List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
- notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
-
- when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0))
- .thenReturn(mFooUser0.mPackageInfo);
-
- mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName());
-
- verify(mListener, times(fooSecondaries.size()))
- .onReconcileSecondaryDexFile(any(ApplicationInfo.class),
- any(DexUseInfo.class), anyString(), anyInt());
- }
-
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
String[] expectedContexts) {
@@ -585,6 +563,10 @@ public class DexManagerTests {
return pui;
}
+ private PackageDynamicCode getPackageDynamicCodeInfo(TestData testData) {
+ return mDexManager.getDexLogger().getPackageDynamicCodeInfo(testData.getPackageName());
+ }
+
private void assertNoUseInfo(TestData testData) {
assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
}
@@ -600,11 +582,11 @@ public class DexManagerTests {
}
private void assertNoDclInfo(TestData testData) {
- assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()));
+ assertNull(getPackageDynamicCodeInfo(testData));
}
private void assertNoDclInfo(TestData testData, int userId) {
- PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName());
+ PackageDynamicCode info = getPackageDynamicCodeInfo(testData);
if (info == null) {
return;
}
@@ -615,7 +597,7 @@ public class DexManagerTests {
}
private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) {
- PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName());
+ PackageDynamicCode info = getPackageDynamicCodeInfo(owner);
assertNotNull("No DCL data for owner " + owner.getPackageName(), info);
for (String path : paths) {
DynamicCodeFile fileInfo = info.mFileUsageMap.get(path);
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 77f6a23c26b1..acf511e38738 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import android.content.Context;
-import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
@@ -175,7 +174,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
@SmallTest
public void testGetBatterySaverPolicy_PolicyQuickDoze_DefaultValueCorrect() {
- testServiceDefaultValue_Off(ServiceType.QUICK_DOZE);
+ testServiceDefaultValue_On(ServiceType.QUICK_DOZE);
}
@SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
index 70fadd101a91..00a62a300487 100644
--- a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
@@ -66,7 +66,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException {
- JSONObject json = new JSONObject("{\"minSdk\":2, \"maxSdk\": 3, \"values\": []}");
+ JSONObject json = new JSONObject("{\"min_sdk\":2, \"max_sdk\": 3, \"values\": {}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet(),
emptyMap());
assertThat(config.minSdk).isEqualTo(2);
@@ -75,7 +75,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigNoMinSdk() throws JSONException {
- JSONObject json = new JSONObject("{\"maxSdk\": 3, \"values\": []}");
+ JSONObject json = new JSONObject("{\"max_sdk\": 3, \"values\": {}}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -86,7 +86,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigNoMaxSdk() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"values\": []}");
+ JSONObject json = new JSONObject("{\"min_sdk\": 1, \"values\": {}}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -97,7 +97,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigNoValues() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3}");
+ JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -108,7 +108,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\":null, \"maxSdk\": 3, \"values\": []}");
+ JSONObject json = new JSONObject("{\"min_sdk\":null, \"max_sdk\": 3, \"values\": {}}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -119,7 +119,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\":1, \"maxSdk\": null, \"values\": []}");
+ JSONObject json = new JSONObject("{\"min_sdk\":1, \"max_sdk\": null, \"values\": {}}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -130,7 +130,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigNullValues() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": null}");
+ JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": null}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -142,7 +142,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigZeroValues()
throws JSONException, InvalidConfigException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": []}");
+ JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": {}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
emptyMap());
assertThat(config.values).hasSize(0);
@@ -152,7 +152,7 @@ public class SignedConfigTest {
public void testParsePerSdkConfigSingleKey()
throws JSONException, InvalidConfigException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
emptyMap());
assertThat(config.values).containsExactly("a", "1");
@@ -162,7 +162,7 @@ public class SignedConfigTest {
public void testParsePerSdkConfigSingleKeyNullValue()
throws JSONException, InvalidConfigException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
emptyMap());
assertThat(config.values.keySet()).containsExactly("a");
@@ -173,8 +173,7 @@ public class SignedConfigTest {
public void testParsePerSdkConfigMultiKeys()
throws JSONException, InvalidConfigException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, "
- + "{\"key\":\"c\", \"value\": \"2\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"c\": \"2\"}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(
json, setOf("a", "b", "c"), emptyMap());
assertThat(config.values).containsExactly("a", "1", "c", "2");
@@ -183,7 +182,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigSingleKeyNotAllowed() throws JSONException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
try {
SignedConfig.parsePerSdkConfig(json, setOf("b"), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -196,7 +195,7 @@ public class SignedConfigTest {
public void testParsePerSdkConfigSingleKeyWithMap()
throws JSONException, InvalidConfigException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
mapOf("a", mapOf("1", "one")));
assertThat(config.values).containsExactly("a", "one");
@@ -205,7 +204,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigSingleKeyWithMapInvalidValue() throws JSONException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"2\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"2\"}}");
try {
SignedConfig.parsePerSdkConfig(json, setOf("b"), mapOf("a", mapOf("1", "one")));
fail("Expected InvalidConfigException");
@@ -218,8 +217,7 @@ public class SignedConfigTest {
public void testParsePerSdkConfigMultiKeysWithMap()
throws JSONException, InvalidConfigException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"},"
- + "{\"key\":\"b\", \"value\": \"1\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"b\": \"1\"}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
mapOf("a", mapOf("1", "one")));
assertThat(config.values).containsExactly("a", "one", "b", "1");
@@ -229,7 +227,7 @@ public class SignedConfigTest {
public void testParsePerSdkConfigSingleKeyWithMapToNull()
throws JSONException, InvalidConfigException {
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
mapOf("a", mapOf("1", null)));
assertThat(config.values).containsExactly("a", null);
@@ -241,25 +239,15 @@ public class SignedConfigTest {
assertThat(mapOf(null, "allitnil")).containsExactly(null, "allitnil");
assertThat(mapOf(null, "allitnil").containsKey(null)).isTrue();
JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}");
+ "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}");
SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
mapOf("a", mapOf(null, "allitnil")));
assertThat(config.values).containsExactly("a", "allitnil");
}
@Test
- public void testParsePerSdkConfigSingleKeyNoValue()
- throws JSONException, InvalidConfigException {
- JSONObject json = new JSONObject(
- "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}");
- SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
- emptyMap());
- assertThat(config.values).containsExactly("a", null);
- }
-
- @Test
public void testParsePerSdkConfigValuesInvalid() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": \"foo\"}");
+ JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": \"foo\"}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -270,18 +258,7 @@ public class SignedConfigTest {
@Test
public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [1, 2]}");
- try {
- SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
- fail("Expected InvalidConfigException or JSONException");
- } catch (JSONException | InvalidConfigException e) {
- // expected
- }
- }
-
- @Test
- public void testParsePerSdkConfigConfigEntryNull() throws JSONException {
- JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [null]}");
+ JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": [1, 2]}");
try {
SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
fail("Expected InvalidConfigException or JSONException");
@@ -361,7 +338,7 @@ public class SignedConfigTest {
@Test
public void testParseSdkConfigSingle() throws InvalidConfigException {
SignedConfig config = SignedConfig.parse(
- "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}",
+ "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}]}",
emptySet(), emptyMap());
assertThat(config.perSdkConfig).hasSize(1);
}
@@ -369,8 +346,8 @@ public class SignedConfigTest {
@Test
public void testParseSdkConfigMultiple() throws InvalidConfigException {
SignedConfig config = SignedConfig.parse(
- "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, "
- + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet(),
+ "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}, "
+ + "{\"min_sdk\": 2, \"max_sdk\": 2, \"values\": {}}]}", emptySet(),
emptyMap());
assertThat(config.perSdkConfig).hasSize(2);
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index bf7f53dd7d8f..860656bf47f4 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -16,6 +16,8 @@
package com.android.server.usage;
+import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE;
+
import static junit.framework.TestCase.fail;
import static org.testng.Assert.assertEquals;
@@ -136,9 +138,11 @@ public class UsageStatsDatabaseTest {
event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
}
- event.mClass = ".fake.class.name" + i % 11;
+ final int instanceId = i % 11;
+ event.mClass = ".fake.class.name" + instanceId;
event.mTimeStamp = time;
- event.mEventType = i % 19; //"random" event type
+ event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
+ event.mInstanceId = instanceId;
switch (event.mEventType) {
case Event.CONFIGURATION_CHANGE:
@@ -160,7 +164,8 @@ public class UsageStatsDatabaseTest {
}
mIntervalStats.events.insert(event);
- mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType);
+ mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
+ event.mInstanceId);
time += timeProgression; // Arbitrary progression of time
}
@@ -219,7 +224,9 @@ public class UsageStatsDatabaseTest {
// mBeginTimeStamp is based on the enclosing IntervalStats, don't bother checking
// mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking
assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed);
+ assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible);
assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground);
+ assertEquals(us1.mTotalTimeVisible, us2.mTotalTimeVisible);
assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed);
assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed);
// mLaunchCount not persisted, so skipped
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index f6871b3182ca..85410f5e14df 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -82,17 +82,17 @@ public class ActivityStackTests extends ActivityTestsBase {
@Test
public void testEmptyTaskCleanupOnRemove() {
- assertNotNull(mTask.getWindowContainerController());
+ assertNotNull(mTask.getTask());
mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
- assertNull(mTask.getWindowContainerController());
+ assertNull(mTask.getTask());
}
@Test
public void testOccupiedTaskCleanupOnRemove() {
final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
- assertNotNull(mTask.getWindowContainerController());
+ assertNotNull(mTask.getTask());
mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
- assertNotNull(mTask.getWindowContainerController());
+ assertNotNull(mTask.getTask());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index f553c35071a4..569c6d4af37d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -345,7 +345,7 @@ class ActivityTestsBase {
mStack.moveToFront("test");
mStack.addTask(task, true, "creating test task");
task.setStack(mStack);
- task.setWindowContainerController();
+ task.setTask();
}
task.touchActiveTime();
@@ -361,12 +361,12 @@ class ActivityTestsBase {
}
@Override
- void createWindowContainer(boolean onTop, boolean showForAllUsers) {
- setWindowContainerController();
+ void createTask(boolean onTop, boolean showForAllUsers) {
+ setTask();
}
- private void setWindowContainerController() {
- setWindowContainerController(mock(TaskWindowContainerController.class));
+ private void setTask() {
+ setTask(mock(Task.class));
}
}
}
@@ -447,7 +447,11 @@ class ActivityTestsBase {
}
@Override
- void updateUsageStats(ActivityRecord component, boolean resumed) {
+ void updateBatteryStats(ActivityRecord component, boolean resumed) {
+ }
+
+ @Override
+ void updateActivityUsageStats(ActivityRecord activity, int event) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java
index ce5b13cab1a7..5690b58737ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -41,17 +41,14 @@ public class StackWindowControllerTests extends WindowTestsBase {
public void testRemoveContainer() {
final StackWindowController stackController =
createStackControllerOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(stackController);
+ final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController);
final TaskStack stack = stackController.mContainer;
- final Task task = taskController.mContainer;
assertNotNull(stack);
assertNotNull(task);
stackController.removeContainer();
// Assert that the container was removed.
assertNull(stackController.mContainer);
- assertNull(taskController.mContainer);
assertNull(stack.getDisplayContent());
assertNull(task.getDisplayContent());
assertNull(task.mStack);
@@ -61,11 +58,9 @@ public class StackWindowControllerTests extends WindowTestsBase {
public void testRemoveContainer_deferRemoval() {
final StackWindowController stackController =
createStackControllerOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(stackController);
+ final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController);
final TaskStack stack = stackController.mContainer;
- final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer;
// Stack removal is deferred if one of its child is animating.
task.setLocalIsAnimating(true);
@@ -75,11 +70,12 @@ public class StackWindowControllerTests extends WindowTestsBase {
// the stack window container is removed.
assertNull(stackController.mContainer);
assertNull(stack.getController());
- assertNotNull(taskController.mContainer);
- assertNotNull(task.getController());
+ assertNotNull(task);
stack.removeImmediately();
- assertNull(taskController.mContainer);
+ // After removing, the task will be isolated.
+ assertNull(task.getParent());
+ assertEquals(task.getChildCount(), 0);
assertNull(task.getController());
}
@@ -89,9 +85,7 @@ public class StackWindowControllerTests extends WindowTestsBase {
final StackWindowController stack1Controller =
createStackControllerOnDisplay(mDisplayContent);
final TaskStack stack1 = stack1Controller.mContainer;
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(stack1Controller);
- final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer;
+ final WindowTestUtils.TestTask task1 = WindowTestUtils.createTestTask(stack1Controller);
task1.mOnDisplayChangedCalled = false;
// Create second display and put second stack on it.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 630a8bffdb30..6b6b33c52d91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
@@ -31,10 +34,12 @@ import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.service.voice.IVoiceInteractionSession;
import android.util.Xml;
+import android.view.DisplayInfo;
import androidx.test.filters.MediumTest;
@@ -66,10 +71,13 @@ public class TaskRecordTests extends ActivityTestsBase {
private static final String TASK_TAG = "task";
+ private Rect mParentBounds;
+
@Before
public void setUp() throws Exception {
TaskRecord.setTaskRecordFactory(null);
setupActivityTaskManagerService();
+ mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
}
@Test
@@ -124,6 +132,72 @@ public class TaskRecordTests extends ActivityTestsBase {
assertTrue(task.returnsToHomeStack());
}
+ /** Ensures that bounds are clipped to their parent. */
+ @Test
+ public void testAppBounds_BoundsClipping() {
+ final Rect shiftedBounds = new Rect(mParentBounds);
+ shiftedBounds.offset(10, 10);
+ final Rect expectedBounds = new Rect(mParentBounds);
+ expectedBounds.intersect(shiftedBounds);
+ testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
+ expectedBounds);
+ }
+
+ /** Ensures that empty bounds are not propagated to the configuration. */
+ @Test
+ public void testAppBounds_EmptyBounds() {
+ final Rect emptyBounds = new Rect();
+ testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
+ null /*ExpectedBounds*/);
+ }
+
+ /** Ensures that bounds on freeform stacks are not clipped. */
+ @Test
+ public void testAppBounds_FreeFormBounds() {
+ final Rect freeFormBounds = new Rect(mParentBounds);
+ freeFormBounds.offset(10, 10);
+ testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
+ freeFormBounds);
+ }
+
+ /** Ensures that fully contained bounds are not clipped. */
+ @Test
+ public void testAppBounds_ContainedBounds() {
+ final Rect insetBounds = new Rect(mParentBounds);
+ insetBounds.inset(5, 5, 5, 5);
+ testStackBoundsConfiguration(
+ WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
+ }
+
+ /** Ensures that full screen free form bounds are clipped */
+ @Test
+ public void testAppBounds_FullScreenFreeFormBounds() {
+ ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayInfo info = new DisplayInfo();
+ display.mDisplay.getDisplayInfo(info);
+ final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
+ testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
+ mParentBounds);
+ }
+
+ private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
+ Rect expectedConfigBounds) {
+
+ ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build();
+
+ final Configuration parentConfig = stack.getConfiguration();
+ parentConfig.windowConfiguration.setAppBounds(parentBounds);
+ task.setBounds(bounds);
+
+ task.resolveOverrideConfiguration(parentConfig);
+ // Assert that both expected and actual are null or are equal to each other
+ assertEquals(expectedConfigBounds,
+ task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds());
+ }
+
private byte[] serializeToBytes(TaskRecord r) throws IOException, XmlPullParserException {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
final XmlSerializer serializer = Xml.newSerializer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
new file mode 100644
index 000000000000..3e32ed37a6f3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test class for {@link Task}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:TaskTests
+ */
+@SmallTest
+@Presubmit
+public class TaskTests extends WindowTestsBase {
+
+ @Test
+ public void testRemoveContainer() {
+ final StackWindowController stackController1 =
+ createStackControllerOnDisplay(mDisplayContent);
+ final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+ final WindowTestUtils.TestAppWindowToken appToken =
+ WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task);
+
+ task.removeIfPossible();
+ // Assert that the container was removed.
+ assertNull(task.getParent());
+ assertEquals(task.getChildCount(), 0);
+ assertNull(appToken.getParent());
+ }
+
+ @Test
+ public void testRemoveContainer_deferRemoval() {
+ final StackWindowController stackController1 =
+ createStackControllerOnDisplay(mDisplayContent);
+ final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+ final WindowTestUtils.TestAppWindowToken appToken =
+ WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task);
+
+ task.mShouldDeferRemoval = true;
+
+ task.removeIfPossible();
+ // For the case of deferred removal the task will still be connected to the its app token
+ // until the task window container is removed.
+ assertNotNull(task.getParent());
+ assertNotEquals(task.getChildCount(), 0);
+ assertNotNull(appToken.getParent());
+
+ task.removeImmediately();
+ assertNull(task.getParent());
+ assertEquals(task.getChildCount(), 0);
+ assertNull(appToken.getParent());
+ }
+
+ @Test
+ public void testReparent() {
+ final StackWindowController stackController1 =
+ createStackControllerOnDisplay(mDisplayContent);
+ final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+ final StackWindowController stackController2 =
+ createStackControllerOnDisplay(mDisplayContent);
+ final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stackController2);
+
+ boolean gotException = false;
+ try {
+ task.reparent(stackController1, 0, false/* moveParents */);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue("Should not be able to reparent to the same parent", gotException);
+
+ final StackWindowController stackController3 =
+ createStackControllerOnDisplay(mDisplayContent);
+ stackController3.setContainer(null);
+ gotException = false;
+ try {
+ task.reparent(stackController3, 0, false/* moveParents */);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue("Should not be able to reparent to a stack that doesn't have a container",
+ gotException);
+
+ task.reparent(stackController2, 0, false/* moveParents */);
+ assertEquals(stackController2.mContainer, task.getParent());
+ assertEquals(0, task.positionInParent());
+ assertEquals(1, task2.positionInParent());
+ }
+
+ @Test
+ public void testReparent_BetweenDisplays() {
+ // Create first stack on primary display.
+ final StackWindowController stack1Controller =
+ createStackControllerOnDisplay(mDisplayContent);
+ final TaskStack stack1 = stack1Controller.mContainer;
+ final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1Controller);
+ task.mOnDisplayChangedCalled = false;
+ assertEquals(mDisplayContent, stack1.getDisplayContent());
+
+ // Create second display and put second stack on it.
+ final DisplayContent dc = createNewDisplay();
+ final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
+ final TaskStack stack2 = stack2Controller.mContainer;
+ final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stack2Controller);
+ // Reparent and check state
+ task.reparent(stack2Controller, 0, false /* moveParents */);
+ assertEquals(stack2, task.getParent());
+ assertEquals(0, task.positionInParent());
+ assertEquals(1, task2.positionInParent());
+ assertTrue(task.mOnDisplayChangedCalled);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
deleted file mode 100644
index bbf508dd1630..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-/**
- * Test class for {@link TaskWindowContainerController}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:TaskWindowContainerControllerTests
- */
-@SmallTest
-@Presubmit
-public class TaskWindowContainerControllerTests extends WindowTestsBase {
-
- /* Comment out due to removal of AppWindowContainerController
- @Test
- public void testRemoveContainer() {
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(this);
- final WindowTestUtils.TestAppWindowContainerController appController =
- new WindowTestUtils.TestAppWindowContainerController(taskController);
-
- taskController.removeContainer();
- // Assert that the container was removed.
- assertNull(taskController.mContainer);
- assertNull(appController.mContainer);
- }
- */
-
- /* Comment out due to removal of AppWindowContainerController
- @Test
- public void testRemoveContainer_deferRemoval() {
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(this);
- final WindowTestUtils.TestAppWindowContainerController appController =
- new WindowTestUtils.TestAppWindowContainerController(taskController);
-
- final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer;
- final AppWindowToken app = appController.mContainer;
- task.mShouldDeferRemoval = true;
-
- taskController.removeContainer();
- // For the case of deferred removal the task controller will no longer be connected to the
- // container, but the app controller will still be connected to the its container until
- // the task window container is removed.
- assertNull(taskController.mContainer);
- assertNull(task.getController());
- assertNotNull(appController.mContainer);
- assertNotNull(app.getController());
-
- task.removeImmediately();
- assertNull(appController.mContainer);
- assertNull(app.getController());
- }
- */
-
- @Test
- public void testReparent() {
- final StackWindowController stackController1 =
- createStackControllerOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(stackController1);
- final StackWindowController stackController2 =
- createStackControllerOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTaskWindowContainerController taskController2 =
- new WindowTestUtils.TestTaskWindowContainerController(stackController2);
-
- boolean gotException = false;
- try {
- taskController.reparent(stackController1, 0, false/* moveParents */);
- } catch (IllegalArgumentException e) {
- gotException = true;
- }
- assertTrue("Should not be able to reparent to the same parent", gotException);
-
- final StackWindowController stackController3 =
- createStackControllerOnDisplay(mDisplayContent);
- stackController3.setContainer(null);
- gotException = false;
- try {
- taskController.reparent(stackController3, 0, false/* moveParents */);
- } catch (IllegalArgumentException e) {
- gotException = true;
- }
- assertTrue("Should not be able to reparent to a stack that doesn't have a container",
- gotException);
-
- taskController.reparent(stackController2, 0, false/* moveParents */);
- assertEquals(stackController2.mContainer, taskController.mContainer.getParent());
- assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent());
- assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent());
- }
-
- @Test
- public void testReparent_BetweenDisplays() {
- // Create first stack on primary display.
- final StackWindowController stack1Controller =
- createStackControllerOnDisplay(mDisplayContent);
- final TaskStack stack1 = stack1Controller.mContainer;
- final WindowTestUtils.TestTaskWindowContainerController taskController =
- new WindowTestUtils.TestTaskWindowContainerController(stack1Controller);
- final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer;
- task1.mOnDisplayChangedCalled = false;
- assertEquals(mDisplayContent, stack1.getDisplayContent());
-
- // Create second display and put second stack on it.
- final DisplayContent dc = createNewDisplay();
- final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
- final TaskStack stack2 = stack2Controller.mContainer;
- final WindowTestUtils.TestTaskWindowContainerController taskController2 =
- new WindowTestUtils.TestTaskWindowContainerController(stack2Controller);
- final WindowTestUtils.TestTask task2 =
- (WindowTestUtils.TestTask) taskController2.mContainer;
-
- // Reparent and check state
- taskController.reparent(stack2Controller, 0, false /* moveParents */);
- assertEquals(stack2, task1.getParent());
- assertEquals(0, task1.positionInParent());
- assertEquals(1, task2.positionInParent());
- assertTrue(task1.mOnDisplayChangedCalled);
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java
index 885a7e02199b..c653762fb96f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOW_CONFIG_ALWAYS_ON_TOP;
import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
import static android.app.WindowConfiguration.WINDOW_CONFIG_ROTATION;
@@ -174,70 +173,4 @@ public class WindowConfigurationTests extends WindowTestsBase {
assertEquals(appBounds.width(), info.appWidth);
assertEquals(appBounds.height(), info.appHeight);
}
-
- /** Ensures that bounds are clipped to their parent. */
- @Test
- public void testAppBounds_BoundsClipping() {
- final Rect shiftedBounds = new Rect(mParentBounds);
- shiftedBounds.offset(10, 10);
- final Rect expectedBounds = new Rect(mParentBounds);
- expectedBounds.intersect(shiftedBounds);
- testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
- expectedBounds);
- }
-
- /** Ensures that empty bounds are not propagated to the configuration. */
- @Test
- public void testAppBounds_EmptyBounds() {
- final Rect emptyBounds = new Rect();
- testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
- null /*ExpectedBounds*/);
- }
-
- /** Ensures that bounds on freeform stacks are not clipped. */
- @Test
- public void testAppBounds_FreeFormBounds() {
- final Rect freeFormBounds = new Rect(mParentBounds);
- freeFormBounds.offset(10, 10);
- testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
- freeFormBounds);
- }
-
- /** Ensures that fully contained bounds are not clipped. */
- @Test
- public void testAppBounds_ContainedBounds() {
- final Rect insetBounds = new Rect(mParentBounds);
- insetBounds.inset(5, 5, 5, 5);
- testStackBoundsConfiguration(
- WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
- }
-
- /** Ensures that full screen free form bounds are clipped */
- @Test
- public void testAppBounds_FullScreenFreeFormBounds() {
- final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth,
- mDisplayInfo.logicalHeight);
- testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
- mParentBounds);
- }
-
- private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
- Rect expectedConfigBounds) {
- final StackWindowController stackController = createStackControllerOnStackOnDisplay(
- windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent);
-
- final Configuration parentConfig = mDisplayContent.getConfiguration();
- parentConfig.windowConfiguration.setAppBounds(parentBounds);
-
- final Configuration config = new Configuration();
- final WindowConfiguration winConfig = config.windowConfiguration;
- stackController.adjustConfigurationForBounds(bounds,
- new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/,
- false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig,
- windowingMode);
- // Assert that both expected and actual are null or are equal to each other
-
- assertEquals(expectedConfigBounds, winConfig.getAppBounds());
- }
-
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 65e18354a5ae..65cde77e7c03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -19,11 +19,6 @@ package com.android.server.wm;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -34,8 +29,6 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.view.Display;
@@ -45,8 +38,6 @@ import android.view.Surface;
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
-import org.mockito.invocation.InvocationOnMock;
-
/**
* A collection of static functions that can be referenced by other test packages to provide access
* to WindowManager related test functionality.
@@ -116,17 +107,6 @@ public class WindowTestUtils {
public static StackWindowController createMockStackWindowContainerController() {
StackWindowController controller = mock(StackWindowController.class);
controller.mContainer = mock(TestTaskStack.class);
-
- // many components rely on the {@link StackWindowController#adjustConfigurationForBounds}
- // to properly set bounds values in the configuration. We must mimick those actions here.
- doAnswer((InvocationOnMock invocationOnMock) -> {
- final Configuration config = invocationOnMock.<Configuration>getArgument(6);
- final Rect bounds = invocationOnMock.<Rect>getArgument(0);
- config.windowConfiguration.setBounds(bounds);
- return null;
- }).when(controller).adjustConfigurationForBounds(any(), any(), any(),
- anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt());
-
return controller;
}
@@ -141,6 +121,13 @@ public class WindowTestUtils {
}
}
+ /** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */
+ public static TestAppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) {
+ final TestAppWindowToken newToken = createTestAppWindowToken(dc);
+ task.addChild(newToken, POSITION_TOP);
+ return newToken;
+ }
+
/**
* An extension of {@link TestTaskStack}, which overrides package scoped methods that would not
* normally be mocked out.
@@ -264,9 +251,10 @@ public class WindowTestUtils {
TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service,
int resizeMode, boolean supportsPictureInPicture,
- TaskWindowContainerController controller) {
+ TaskRecord taskRecord) {
super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture,
- new ActivityManager.TaskDescription(), controller);
+ new ActivityManager.TaskDescription(), taskRecord);
+ stack.addTask(this, POSITION_TOP);
}
boolean shouldDeferRemoval() {
@@ -293,49 +281,10 @@ public class WindowTestUtils {
}
}
- /**
- * Used so we can gain access to some protected members of {@link TaskWindowContainerController}
- * class.
- */
- public static class TestTaskWindowContainerController extends TaskWindowContainerController {
-
- static final TaskWindowContainerListener NOP_LISTENER = new TaskWindowContainerListener() {
- @Override
- public void registerConfigurationChangeListener(
- ConfigurationContainerListener listener) {
- }
-
- @Override
- public void unregisterConfigurationChangeListener(
- ConfigurationContainerListener listener) {
- }
-
- @Override
- public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) {
- }
-
- @Override
- public void requestResize(Rect bounds, int resizeMode) {
- }
- };
-
- TestTaskWindowContainerController(WindowTestsBase testsBase) {
- this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent));
- }
-
- TestTaskWindowContainerController(StackWindowController stackController) {
- super(sNextTaskId++, NOP_LISTENER, stackController, 0 /* userId */, null /* bounds */,
- RESIZE_MODE_UNRESIZEABLE, false /* supportsPictureInPicture */, true /* toTop*/,
- true /* showForAllUsers */, new ActivityManager.TaskDescription(),
- stackController.mService);
- }
-
- @Override
- TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode,
- boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) {
- return new TestTask(taskId, stack, userId, mService, resizeMode,
- supportsPictureInPicture, this);
- }
+ public static TestTask createTestTask(StackWindowController stackWindowController) {
+ return new TestTask(sNextTaskId++, stackWindowController.mContainer, 0,
+ stackWindowController.mService, RESIZE_MODE_UNRESIZEABLE, false,
+ mock(TaskRecord.class));
}
public static class TestIApplicationToken implements IApplicationToken {
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 4f573a475ae7..65ed85db17bd 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -842,8 +842,8 @@ public class AppStandbyController {
final boolean previouslyIdle = mAppIdleHistory.isIdle(
event.mPackage, userId, elapsedRealtime);
// Inform listeners if necessary
- if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
- || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
+ if ((event.mEventType == UsageEvents.Event.ACTIVITY_RESUMED
+ || event.mEventType == UsageEvents.Event.ACTIVITY_PAUSED
|| event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
|| event.mEventType == UsageEvents.Event.USER_INTERACTION
|| event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
@@ -894,8 +894,8 @@ public class AppStandbyController {
private int usageEventToSubReason(int eventType) {
switch (eventType) {
- case UsageEvents.Event.MOVE_TO_FOREGROUND: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
- case UsageEvents.Event.MOVE_TO_BACKGROUND: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
+ case UsageEvents.Event.ACTIVITY_RESUMED: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
+ case UsageEvents.Event.ACTIVITY_PAUSED: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION;
case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION;
case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN;
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 84052672f6d3..94cc6502a12d 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -15,10 +15,31 @@
*/
package com.android.server.usage;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
+import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
+import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
+import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
+import static android.app.usage.UsageEvents.Event.END_OF_DAY;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
+import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
+import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
+import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN;
+import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN;
+import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
+import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE;
+import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE;
+import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED;
+import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
+
import android.app.usage.ConfigurationStats;
import android.app.usage.EventList;
import android.app.usage.EventStats;
-import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.util.ArrayMap;
@@ -125,8 +146,8 @@ public class IntervalStats {
/**
* Builds a UsageEvents.Event, but does not add it internally.
*/
- UsageEvents.Event buildEvent(String packageName, String className) {
- UsageEvents.Event event = new UsageEvents.Event();
+ Event buildEvent(String packageName, String className) {
+ Event event = new Event();
event.mPackage = getCachedStringRef(packageName);
if (className != null) {
event.mClass = getCachedStringRef(className);
@@ -138,9 +159,9 @@ public class IntervalStats {
* Builds a UsageEvents.Event from a proto, but does not add it internally.
* Built here to take advantage of the cached String Refs
*/
- UsageEvents.Event buildEvent(ProtoInputStream parser, List<String> stringPool)
+ Event buildEvent(ProtoInputStream parser, List<String> stringPool)
throws IOException {
- final UsageEvents.Event event = new UsageEvents.Event();
+ final Event event = new Event();
while (true) {
switch (parser.nextField()) {
case (int) IntervalStatsProto.Event.PACKAGE:
@@ -190,20 +211,23 @@ public class IntervalStats {
parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX)
- 1));
break;
+ case (int) IntervalStatsProto.Event.INSTANCE_ID:
+ event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
+ break;
case ProtoInputStream.NO_MORE_FIELDS:
// Handle default values for certain events types
switch (event.mEventType) {
- case UsageEvents.Event.CONFIGURATION_CHANGE:
+ case CONFIGURATION_CHANGE:
if (event.mConfiguration == null) {
event.mConfiguration = new Configuration();
}
break;
- case UsageEvents.Event.SHORTCUT_INVOCATION:
+ case SHORTCUT_INVOCATION:
if (event.mShortcutId == null) {
event.mShortcutId = "";
}
break;
- case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+ case NOTIFICATION_INTERRUPTION:
if (event.mNotificationChannelId == null) {
event.mNotificationChannelId = "";
}
@@ -220,14 +244,15 @@ public class IntervalStats {
private boolean isStatefulEvent(int eventType) {
switch (eventType) {
- case UsageEvents.Event.MOVE_TO_FOREGROUND:
- case UsageEvents.Event.MOVE_TO_BACKGROUND:
- case UsageEvents.Event.FOREGROUND_SERVICE_START:
- case UsageEvents.Event.FOREGROUND_SERVICE_STOP:
- case UsageEvents.Event.END_OF_DAY:
- case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE:
- case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
- case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE:
+ case ACTIVITY_RESUMED:
+ case ACTIVITY_PAUSED:
+ case ACTIVITY_STOPPED:
+ case FOREGROUND_SERVICE_START:
+ case FOREGROUND_SERVICE_STOP:
+ case END_OF_DAY:
+ case ROLLOVER_FOREGROUND_SERVICE:
+ case CONTINUE_PREVIOUS_DAY:
+ case CONTINUING_FOREGROUND_SERVICE:
return true;
}
return false;
@@ -238,17 +263,56 @@ public class IntervalStats {
* interaction. Excludes those that are internally generated.
*/
private boolean isUserVisibleEvent(int eventType) {
- return eventType != UsageEvents.Event.SYSTEM_INTERACTION
- && eventType != UsageEvents.Event.STANDBY_BUCKET_CHANGED;
+ return eventType != SYSTEM_INTERACTION
+ && eventType != STANDBY_BUCKET_CHANGED;
}
/**
+ * Update the IntervalStats by a activity or foreground service event.
+ * @param packageName package name of this event. Is null if event targets to all packages.
+ * @param className class name of a activity or foreground service, could be null to if this
+ * is sent to all activities/services in this package.
+ * @param timeStamp Epoch timestamp in milliseconds.
+ * @param eventType event type as in {@link Event}
+ * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
+ * if className is not an activity, instanceId is not used.
* @hide
*/
@VisibleForTesting
- public void update(String packageName, String className, long timeStamp, int eventType) {
- UsageStats usageStats = getOrCreateUsageStats(packageName);
- usageStats.update(className, timeStamp, eventType);
+ public void update(String packageName, String className, long timeStamp, int eventType,
+ int instanceId) {
+ if (eventType == FLUSH_TO_DISK) {
+ // FLUSH_TO_DISK are sent to all packages.
+ final int size = packageStats.size();
+ for (int i = 0; i < size; i++) {
+ UsageStats usageStats = packageStats.valueAt(i);
+ usageStats.update(null, timeStamp, eventType, instanceId);
+ }
+ } else if (eventType == ACTIVITY_DESTROYED) {
+ UsageStats usageStats = packageStats.get(packageName);
+ if (usageStats != null) {
+ // If previous event is not ACTIVITY_STOPPED, convert ACTIVITY_DESTROYED
+ // to ACTIVITY_STOPPED and add to event list.
+ // Otherwise do not add anything to event list. (Because we want to save space
+ // and we do not want a ACTIVITY_STOPPED followed by
+ // ACTIVITY_DESTROYED in event list).
+ final int index = usageStats.mActivities.indexOfKey(instanceId);
+ if (index >= 0) {
+ final int type = usageStats.mActivities.valueAt(index);
+ if (type != ACTIVITY_STOPPED) {
+ Event event = new Event(ACTIVITY_STOPPED, timeStamp);
+ event.mPackage = packageName;
+ event.mClass = className;
+ event.mInstanceId = instanceId;
+ addEvent(event);
+ }
+ }
+ usageStats.update(className, timeStamp, ACTIVITY_DESTROYED, instanceId);
+ }
+ } else {
+ UsageStats usageStats = getOrCreateUsageStats(packageName);
+ usageStats.update(className, timeStamp, eventType, instanceId);
+ }
endTime = timeStamp;
}
@@ -256,7 +320,7 @@ public class IntervalStats {
* @hide
*/
@VisibleForTesting
- public void addEvent(UsageEvents.Event event) {
+ public void addEvent(Event event) {
if (events == null) {
events = new EventList();
}
@@ -265,7 +329,7 @@ public class IntervalStats {
if (event.mClass != null) {
event.mClass = getCachedStringRef(event.mClass);
}
- if (event.mEventType == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
+ if (event.mEventType == NOTIFICATION_INTERRUPTION) {
event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
}
events.insert(event);
@@ -338,13 +402,13 @@ public class IntervalStats {
}
void addEventStatsTo(List<EventStats> out) {
- interactiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_INTERACTIVE,
+ interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE,
beginTime, endTime);
- nonInteractiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_NON_INTERACTIVE,
+ nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE,
beginTime, endTime);
- keyguardShownTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_SHOWN,
+ keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN,
beginTime, endTime);
- keyguardHiddenTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_HIDDEN,
+ keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN,
beginTime, endTime);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 8e1c06091605..d70653781eff 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -135,6 +135,15 @@ final class UsageStatsProto {
stats.mTotalTimeForegroundServiceUsed = proto.readLong(
IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS);
break;
+ case (int) IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS:
+ // Time attributes stored is an offset of the beginTime.
+ stats.mLastTimeVisible = statsOut.beginTime + proto.readLong(
+ IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS);
+ break;
+ case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS:
+ stats.mTotalTimeVisible = proto.readLong(
+ IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS);
+ break;
}
}
if (stats.mLastTimeUsed == 0) {
@@ -337,6 +346,11 @@ final class UsageStatsProto {
usageStats.mLastTimeForegroundServiceUsed - stats.beginTime);
proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS,
usageStats.mTotalTimeForegroundServiceUsed);
+ // Time attributes stored as an offset of the beginTime.
+ proto.write(IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS,
+ usageStats.mLastTimeVisible - stats.beginTime);
+ proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS,
+ usageStats.mTotalTimeVisible);
proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount);
writeChooserCounts(proto, usageStats);
proto.end(token);
@@ -427,6 +441,7 @@ final class UsageStatsProto {
proto.write(IntervalStatsProto.Event.TIME_MS, event.mTimeStamp - stats.beginTime);
proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
+ proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 262125212c14..57dc08fcd253 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -16,6 +16,12 @@
package com.android.server.usage;
+import static android.app.usage.UsageEvents.Event.CHOOSER_ACTION;
+import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
+import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+
import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -28,10 +34,10 @@ import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManager;
-import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -75,9 +81,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
/**
* A service that collects, aggregates, and persists application usage data.
@@ -106,6 +110,7 @@ public class UsageStatsService extends SystemService implements
static final int MSG_FLUSH_TO_DISK = 1;
static final int MSG_REMOVE_USER = 2;
static final int MSG_UID_STATE_CHANGED = 3;
+ static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
private final Object mLock = new Object();
Handler mHandler;
@@ -135,12 +140,10 @@ public class UsageStatsService extends SystemService implements
@Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
int bucket, int reason) {
- Event event = new Event();
- event.mEventType = Event.STANDBY_BUCKET_CHANGED;
+ Event event = new Event(Event.STANDBY_BUCKET_CHANGED,
+ SystemClock.elapsedRealtime());
event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF);
event.mPackage = packageName;
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -397,7 +400,7 @@ public class UsageStatsService extends SystemService implements
* Assuming the event's timestamp is measured in milliseconds since boot,
* convert it to a system wall time.
*/
- private void convertToSystemTimeLocked(UsageEvents.Event event) {
+ private void convertToSystemTimeLocked(Event event) {
event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
}
@@ -406,7 +409,6 @@ public class UsageStatsService extends SystemService implements
*/
void shutdown() {
synchronized (mLock) {
- mHandler.removeMessages(MSG_REPORT_EVENT);
flushToDiskLocked();
}
}
@@ -414,7 +416,7 @@ public class UsageStatsService extends SystemService implements
/**
* Called by the Binder stub.
*/
- void reportEvent(UsageEvents.Event event, int userId) {
+ void reportEvent(Event event, int userId) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -431,14 +433,14 @@ public class UsageStatsService extends SystemService implements
mAppStandby.reportEvent(event, elapsedRealtime, userId);
switch (event.mEventType) {
- case Event.MOVE_TO_FOREGROUND:
+ case Event.ACTIVITY_RESUMED:
try {
mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
} catch (IllegalArgumentException iae) {
Slog.e(TAG, "Failed to note usage start", iae);
}
break;
- case Event.MOVE_TO_BACKGROUND:
+ case Event.ACTIVITY_PAUSED:
try {
mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
} catch (IllegalArgumentException iae) {
@@ -450,10 +452,31 @@ public class UsageStatsService extends SystemService implements
}
/**
- * Called by the Binder stub.
+ * Some events like FLUSH_TO_DISK need to be sent to all userId.
+ * @param event
+ */
+ void reportEventToAllUserId(Event event) {
+ synchronized (mLock) {
+ final int userCount = mUserState.size();
+ for (int i = 0; i < userCount; i++) {
+ Event copy = new Event(event);
+ reportEvent(copy, mUserState.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Called by the Handler for message MSG_FLUSH_TO_DISK.
*/
void flushToDisk() {
synchronized (mLock) {
+ // Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app
+ // usage. In case of abrupt power shutdown like battery drain or cold temperature,
+ // all UsageStats has correct data up to last flush to disk.
+ // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats'
+ // EventList.
+ Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime());
+ reportEventToAllUserId(event);
flushToDiskLocked();
}
}
@@ -656,9 +679,11 @@ public class UsageStatsService extends SystemService implements
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REPORT_EVENT:
- reportEvent((UsageEvents.Event) msg.obj, msg.arg1);
+ reportEvent((Event) msg.obj, msg.arg1);
+ break;
+ case MSG_REPORT_EVENT_TO_ALL_USERID:
+ reportEventToAllUserId((Event) msg.obj);
break;
-
case MSG_FLUSH_TO_DISK:
flushToDisk();
break;
@@ -1120,20 +1145,11 @@ public class UsageStatsService extends SystemService implements
return;
}
- UsageEvents.Event event = new UsageEvents.Event();
+ Event event = new Event(CHOOSER_ACTION, SystemClock.elapsedRealtime());
event.mPackage = packageName;
-
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
-
- event.mEventType = Event.CHOOSER_ACTION;
-
event.mAction = action;
-
event.mContentType = contentType;
-
event.mContentAnnotations = annotations;
-
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -1251,37 +1267,29 @@ public class UsageStatsService extends SystemService implements
private final class LocalService extends UsageStatsManagerInternal {
@Override
- public void reportEvent(ComponentName component, int userId, int eventType) {
+ public void reportEvent(ComponentName component, int userId, int eventType,
+ int instanceId) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
}
- UsageEvents.Event event = new UsageEvents.Event();
+ Event event = new Event(eventType, SystemClock.elapsedRealtime());
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
-
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
-
- event.mEventType = eventType;
+ event.mInstanceId = instanceId;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportEvent(String packageName, int userId, int eventType) {
if (packageName == null) {
- Slog.w(TAG, "Event reported without a package name");
+ Slog.w(TAG, "Event reported without a package name, eventType:" + eventType);
return;
}
- UsageEvents.Event event = new UsageEvents.Event();
+ Event event = new Event(eventType, SystemClock.elapsedRealtime());
event.mPackage = packageName;
-
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
-
- event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -1292,13 +1300,8 @@ public class UsageStatsService extends SystemService implements
return;
}
- UsageEvents.Event event = new UsageEvents.Event();
+ Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime());
event.mPackage = "android";
-
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
-
- event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -1311,14 +1314,9 @@ public class UsageStatsService extends SystemService implements
return;
}
- UsageEvents.Event event = new UsageEvents.Event();
+ Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime());
event.mPackage = packageName.intern();
event.mNotificationChannelId = channelId.intern();
-
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
-
- event.mEventType = Event.NOTIFICATION_INTERRUPTION;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -1329,14 +1327,9 @@ public class UsageStatsService extends SystemService implements
return;
}
- UsageEvents.Event event = new UsageEvents.Event();
+ Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime());
event.mPackage = packageName.intern();
event.mShortcutId = shortcutId.intern();
-
- // This will later be converted to system time.
- event.mTimeStamp = SystemClock.elapsedRealtime();
-
- event.mEventType = Event.SHORTCUT_INVOCATION;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -1372,7 +1365,7 @@ public class UsageStatsService extends SystemService implements
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
// we are shutting down.
- shutdown();
+ UsageStatsService.this.shutdown();
}
@Override
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index 01e566cdd85f..ec475bf26bb3 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -61,11 +61,13 @@ final class UsageStatsXmlV1 {
private static final String FLAGS_ATTR = "flags";
private static final String CLASS_ATTR = "class";
private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
+ private static final String TOTAL_TIME_VISIBLE_ATTR = "timeVisible";
private static final String TOTAL_TIME_SERVICE_USED_ATTR = "timeServiceUsed";
private static final String COUNT_ATTR = "count";
private static final String ACTIVE_ATTR = "active";
private static final String LAST_EVENT_ATTR = "lastEvent";
private static final String TYPE_ATTR = "type";
+ private static final String INSTANCE_ID_ATTR = "instanceId";
private static final String SHORTCUT_ID_ATTR = "shortcutId";
private static final String STANDBY_BUCKET_ATTR = "standbyBucket";
private static final String APP_LAUNCH_COUNT_ATTR = "appLaunchCount";
@@ -75,6 +77,7 @@ final class UsageStatsXmlV1 {
// Time attributes stored as an offset of the beginTime.
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+ private static final String LAST_TIME_VISIBLE_ATTR = "lastTimeVisible";
private static final String LAST_TIME_SERVICE_USED_ATTR = "lastTimeServiceUsed";
private static final String END_TIME_ATTR = "endTime";
private static final String TIME_ATTR = "time";
@@ -92,6 +95,13 @@ final class UsageStatsXmlV1 {
parser, LAST_TIME_ACTIVE_ATTR);
try {
+ stats.mLastTimeVisible = statsOut.beginTime + XmlUtils.readLongAttribute(
+ parser, LAST_TIME_VISIBLE_ATTR);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to parse mLastTimeVisible", e);
+ }
+
+ try {
stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
parser, LAST_TIME_SERVICE_USED_ATTR);
} catch (IOException e) {
@@ -101,8 +111,14 @@ final class UsageStatsXmlV1 {
stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
try {
+ stats.mTotalTimeVisible = XmlUtils.readLongAttribute(parser, TOTAL_TIME_VISIBLE_ATTR);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to parse mTotalTimeVisible", e);
+ }
+
+ try {
stats.mTotalTimeForegroundServiceUsed = XmlUtils.readLongAttribute(parser,
- TOTAL_TIME_SERVICE_USED_ATTR);
+ TOTAL_TIME_SERVICE_USED_ATTR);
} catch (IOException e) {
Log.e(TAG, "Failed to parse mTotalTimeForegroundServiceUsed", e);
}
@@ -199,6 +215,13 @@ final class UsageStatsXmlV1 {
event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
+
+ try {
+ event.mInstanceId = XmlUtils.readIntAttribute(parser, INSTANCE_ID_ATTR);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to parse mInstanceId", e);
+ }
+
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
event.mConfiguration = new Configuration();
@@ -227,10 +250,13 @@ final class UsageStatsXmlV1 {
// Write the time offset.
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
usageStats.mLastTimeUsed - stats.beginTime);
+ XmlUtils.writeLongAttribute(xml, LAST_TIME_VISIBLE_ATTR,
+ usageStats.mLastTimeVisible - stats.beginTime);
XmlUtils.writeLongAttribute(xml, LAST_TIME_SERVICE_USED_ATTR,
usageStats.mLastTimeForegroundServiceUsed - stats.beginTime);
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
+ XmlUtils.writeLongAttribute(xml, TOTAL_TIME_VISIBLE_ATTR, usageStats.mTotalTimeVisible);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_SERVICE_USED_ATTR,
usageStats.mTotalTimeForegroundServiceUsed);
XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
@@ -317,6 +343,7 @@ final class UsageStatsXmlV1 {
}
XmlUtils.writeIntAttribute(xml, FLAGS_ATTR, event.mFlags);
XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
+ XmlUtils.writeIntAttribute(xml, INSTANCE_ID_ATTR, event.mInstanceId);
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 94d7dbb49b74..5128ae1f9130 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -16,10 +16,18 @@
package com.android.server.usage;
+import static android.app.usage.UsageStatsManager.INTERVAL_BEST;
+import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
+import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
+import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY;
+import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY;
+import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY;
+
import android.app.usage.ConfigurationStats;
import android.app.usage.EventList;
import android.app.usage.EventStats;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.Context;
@@ -29,6 +37,7 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseIntArray;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.usage.UsageStatsDatabase.StatCombiner;
@@ -65,7 +74,7 @@ class UserUsageStatsService {
private final int mUserId;
// STOPSHIP: Temporary member variable for debugging b/110930764.
- private UsageEvents.Event mLastEvent;
+ private Event mLastEvent;
private static final long[] INTERVAL_LENGTH = new long[] {
UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
@@ -87,7 +96,7 @@ class UserUsageStatsService {
mContext = context;
mDailyExpiryDate = new UnixCalendar(0);
mDatabase = new UsageStatsDatabase(usageStatsDir);
- mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
+ mCurrentStats = new IntervalStats[INTERVAL_COUNT];
mListener = listener;
mLogPrefix = "User[" + Integer.toString(userId) + "] ";
mUserId = userId;
@@ -125,28 +134,6 @@ class UserUsageStatsService {
updateRolloverDeadline();
}
- // Now close off any events that were open at the time this was saved.
- for (IntervalStats stat : mCurrentStats) {
- final int pkgCount = stat.packageStats.size();
- for (int i = 0; i < pkgCount; i++) {
- final UsageStats pkgStats = stat.packageStats.valueAt(i);
- if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()
- || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
- if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) {
- stat.update(pkgStats.mPackageName, null, stat.lastTimeSaved,
- UsageEvents.Event.END_OF_DAY);
- }
- if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
- stat.update(pkgStats.mPackageName, null , stat.lastTimeSaved,
- UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE);
- }
- notifyStatsChanged();
- }
- }
-
- stat.updateConfigurationStats(null, stat.lastTimeSaved);
- }
-
if (mDatabase.isNewUpdate()) {
notifyNewUpdate();
}
@@ -158,40 +145,46 @@ class UserUsageStatsService {
loadActiveStats(newTime);
}
- void reportEvent(UsageEvents.Event event) {
+ void reportEvent(Event event) {
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
+ "[" + event.mTimeStamp + "]: "
+ eventToString(event.mEventType));
}
- mLastEvent = new UsageEvents.Event(event);
+ mLastEvent = new Event(event);
if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
// Need to rollover
rolloverStats(event.mTimeStamp);
}
- final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
+ final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
final Configuration newFullConfig = event.mConfiguration;
- if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
- currentDailyStats.activeConfiguration != null) {
+ if (event.mEventType == Event.CONFIGURATION_CHANGE
+ && currentDailyStats.activeConfiguration != null) {
// Make the event configuration a delta.
event.mConfiguration = Configuration.generateDelta(
currentDailyStats.activeConfiguration, newFullConfig);
}
- if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
+ if (event.mEventType != Event.SYSTEM_INTERACTION
+ // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED
+ // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to
+ // ACTIVITY_STOPPED.
+ && event.mEventType != Event.ACTIVITY_DESTROYED
+ // FLUSH_TO_DISK is a private event.
+ && event.mEventType != Event.FLUSH_TO_DISK) {
currentDailyStats.addEvent(event);
}
boolean incrementAppLaunch = false;
- if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
+ if (event.mEventType == Event.ACTIVITY_RESUMED) {
if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) {
incrementAppLaunch = true;
}
- } else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
+ } else if (event.mEventType == Event.ACTIVITY_PAUSED) {
if (event.mPackage != null) {
mLastBackgroundedPackage = event.mPackage;
}
@@ -199,10 +192,10 @@ class UserUsageStatsService {
for (IntervalStats stats : mCurrentStats) {
switch (event.mEventType) {
- case UsageEvents.Event.CONFIGURATION_CHANGE: {
+ case Event.CONFIGURATION_CHANGE: {
stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
} break;
- case UsageEvents.Event.CHOOSER_ACTION: {
+ case Event.CHOOSER_ACTION: {
stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction);
String[] annotations = event.mContentAnnotations;
if (annotations != null) {
@@ -211,21 +204,21 @@ class UserUsageStatsService {
}
}
} break;
- case UsageEvents.Event.SCREEN_INTERACTIVE: {
+ case Event.SCREEN_INTERACTIVE: {
stats.updateScreenInteractive(event.mTimeStamp);
} break;
- case UsageEvents.Event.SCREEN_NON_INTERACTIVE: {
+ case Event.SCREEN_NON_INTERACTIVE: {
stats.updateScreenNonInteractive(event.mTimeStamp);
} break;
- case UsageEvents.Event.KEYGUARD_SHOWN: {
+ case Event.KEYGUARD_SHOWN: {
stats.updateKeyguardShown(event.mTimeStamp);
} break;
- case UsageEvents.Event.KEYGUARD_HIDDEN: {
+ case Event.KEYGUARD_HIDDEN: {
stats.updateKeyguardHidden(event.mTimeStamp);
} break;
default: {
stats.update(event.mPackage, event.getClassName(),
- event.mTimeStamp, event.mEventType);
+ event.mTimeStamp, event.mEventType, event.mInstanceId);
if (incrementAppLaunch) {
stats.incrementAppLaunchCount(event.mPackage);
}
@@ -286,12 +279,12 @@ class UserUsageStatsService {
*/
private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
StatCombiner<T> combiner) {
- if (intervalType == UsageStatsManager.INTERVAL_BEST) {
+ if (intervalType == INTERVAL_BEST) {
intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
if (intervalType < 0) {
// Nothing saved to disk yet, so every stat is just as equal (no rollover has
// occurred.
- intervalType = UsageStatsManager.INTERVAL_DAILY;
+ intervalType = INTERVAL_DAILY;
}
}
@@ -316,10 +309,10 @@ class UserUsageStatsService {
}
// STOPSHIP: Temporary logging for b/110930764.
- if (intervalType == UsageStatsManager.INTERVAL_DAILY
+ if (intervalType == INTERVAL_DAILY
&& mLastEvent != null && mLastEvent.mTimeStamp >= beginTime) {
final IntervalStats diskStats = mDatabase.getLatestUsageStats(
- UsageStatsManager.INTERVAL_DAILY);
+ INTERVAL_DAILY);
StringBuilder sb = new StringBuilder(256);
sb.append("Last 24 hours of UsageStats missing! timeRange : ");
sb.append(beginTime);
@@ -395,11 +388,11 @@ class UserUsageStatsService {
UsageEvents queryEvents(final long beginTime, final long endTime,
boolean obfuscateInstantApps) {
final ArraySet<String> names = new ArraySet<>();
- List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
- beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
+ List<Event> results = queryStats(INTERVAL_DAILY,
+ beginTime, endTime, new StatCombiner<Event>() {
@Override
public void combine(IntervalStats stats, boolean mutable,
- List<UsageEvents.Event> accumulatedResult) {
+ List<Event> accumulatedResult) {
if (stats.events == null) {
return;
}
@@ -411,11 +404,13 @@ class UserUsageStatsService {
return;
}
- UsageEvents.Event event = stats.events.get(i);
+ Event event = stats.events.get(i);
if (obfuscateInstantApps) {
event = event.getObfuscatedIfInstantApp();
}
- names.add(event.mPackage);
+ if (event.mPackage != null) {
+ names.add(event.mPackage);
+ }
if (event.mClass != null) {
names.add(event.mClass);
}
@@ -437,7 +432,7 @@ class UserUsageStatsService {
final String packageName) {
final ArraySet<String> names = new ArraySet<>();
names.add(packageName);
- final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
+ final List<Event> results = queryStats(INTERVAL_DAILY,
beginTime, endTime, (stats, mutable, accumulatedResult) -> {
if (stats.events == null) {
return;
@@ -450,7 +445,7 @@ class UserUsageStatsService {
return;
}
- final UsageEvents.Event event = stats.events.get(i);
+ final Event event = stats.events.get(i);
if (!packageName.equals(event.mPackage)) {
continue;
}
@@ -492,33 +487,33 @@ class UserUsageStatsService {
// Make a note of which components need a new CONTINUE_PREVIOUS_DAY or
// CONTINUING_FOREGROUND_SERVICE entry.
final Configuration previousConfig =
- mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
- ArraySet<String> continuePreviousDay = new ArraySet<>();
- ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundActivity =
+ mCurrentStats[INTERVAL_DAILY].activeConfiguration;
+ ArraySet<String> continuePkgs = new ArraySet<>();
+ ArrayMap<String, SparseIntArray> continueActivity =
new ArrayMap<>();
- ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundService =
+ ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService =
new ArrayMap<>();
for (IntervalStats stat : mCurrentStats) {
final int pkgCount = stat.packageStats.size();
for (int i = 0; i < pkgCount; i++) {
final UsageStats pkgStats = stat.packageStats.valueAt(i);
- if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()
- || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
- if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) {
- continuePreviousDayForegroundActivity.put(pkgStats.mPackageName,
- pkgStats.mLastForegroundActivityEventMap);
+ if (pkgStats.mActivities.size() > 0
+ || !pkgStats.mForegroundServices.isEmpty()) {
+ if (pkgStats.mActivities.size() > 0) {
+ continueActivity.put(pkgStats.mPackageName,
+ pkgStats.mActivities);
stat.update(pkgStats.mPackageName, null,
mDailyExpiryDate.getTimeInMillis() - 1,
- UsageEvents.Event.END_OF_DAY);
+ Event.END_OF_DAY, 0);
}
- if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
- continuePreviousDayForegroundService.put(pkgStats.mPackageName,
- pkgStats.mLastForegroundServiceEventMap);
+ if (!pkgStats.mForegroundServices.isEmpty()) {
+ continueForegroundService.put(pkgStats.mPackageName,
+ pkgStats.mForegroundServices);
stat.update(pkgStats.mPackageName, null,
mDailyExpiryDate.getTimeInMillis() - 1,
- UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE);
+ Event.ROLLOVER_FOREGROUND_SERVICE, 0);
}
- continuePreviousDay.add(pkgStats.mPackageName);
+ continuePkgs.add(pkgStats.mPackageName);
notifyStatsChanged();
}
}
@@ -532,27 +527,27 @@ class UserUsageStatsService {
mDatabase.prune(currentTimeMillis);
loadActiveStats(currentTimeMillis);
- final int continueCount = continuePreviousDay.size();
+ final int continueCount = continuePkgs.size();
for (int i = 0; i < continueCount; i++) {
- String pkgName = continuePreviousDay.valueAt(i);
- final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
+ String pkgName = continuePkgs.valueAt(i);
+ final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime;
for (IntervalStats stat : mCurrentStats) {
- if (continuePreviousDayForegroundActivity.containsKey(pkgName)) {
- final ArrayMap<String, Integer> foregroundActivityEventMap =
- continuePreviousDayForegroundActivity.get(pkgName);
- final int size = foregroundActivityEventMap.size();
+ if (continueActivity.containsKey(pkgName)) {
+ final SparseIntArray eventMap =
+ continueActivity.get(pkgName);
+ final int size = eventMap.size();
for (int j = 0; j < size; j++) {
- stat.update(pkgName, foregroundActivityEventMap.keyAt(j), beginTime,
- UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
+ stat.update(pkgName, null, beginTime,
+ eventMap.valueAt(j), eventMap.keyAt(j));
}
}
- if (continuePreviousDayForegroundService.containsKey(pkgName)) {
- final ArrayMap<String, Integer> foregroundServiceEventMap =
- continuePreviousDayForegroundService.get(pkgName);
- final int size = foregroundServiceEventMap.size();
+ if (continueForegroundService.containsKey(pkgName)) {
+ final ArrayMap<String, Integer> eventMap =
+ continueForegroundService.get(pkgName);
+ final int size = eventMap.size();
for (int j = 0; j < size; j++) {
- stat.update(pkgName, foregroundServiceEventMap.keyAt(j), beginTime,
- UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE);
+ stat.update(pkgName, eventMap.keyAt(j), beginTime,
+ eventMap.valueAt(j), 0);
}
}
stat.updateConfigurationStats(previousConfig, beginTime);
@@ -611,7 +606,7 @@ class UserUsageStatsService {
private void updateRolloverDeadline() {
mDailyExpiryDate.setTimeInMillis(
- mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
+ mCurrentStats[INTERVAL_DAILY].beginTime);
mDailyExpiryDate.addDays(1);
Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
@@ -660,7 +655,7 @@ class UserUsageStatsService {
}
- void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) {
+ void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
pw.printPair("type", eventToString(event.mEventType));
pw.printPair("package", event.mPackage);
@@ -673,10 +668,15 @@ class UserUsageStatsService {
if (event.mShortcutId != null) {
pw.printPair("shortcutId", event.mShortcutId);
}
- if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
+ if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) {
pw.printPair("standbyBucket", event.getStandbyBucket());
pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
+ } else if (event.mEventType == Event.ACTIVITY_RESUMED
+ || event.mEventType == Event.ACTIVITY_PAUSED
+ || event.mEventType == Event.ACTIVITY_STOPPED) {
+ pw.printPair("instanceId", event.getInstanceId());
}
+
if (event.mNotificationChannelId != null) {
pw.printPair("channelId", event.mNotificationChannelId);
}
@@ -691,11 +691,11 @@ class UserUsageStatsService {
final long beginTime = yesterday.getTimeInMillis();
- List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY,
- beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
+ List<Event> events = queryStats(INTERVAL_DAILY,
+ beginTime, endTime, new StatCombiner<Event>() {
@Override
public void combine(IntervalStats stats, boolean mutable,
- List<UsageEvents.Event> accumulatedResult) {
+ List<Event> accumulatedResult) {
if (stats.events == null) {
return;
}
@@ -707,7 +707,7 @@ class UserUsageStatsService {
return;
}
- UsageEvents.Event event = stats.events.get(i);
+ Event event = stats.events.get(i);
if (pkg != null && !pkg.equals(event.mPackage)) {
continue;
}
@@ -727,7 +727,7 @@ class UserUsageStatsService {
pw.println(")");
if (events != null) {
pw.increaseIndent();
- for (UsageEvents.Event event : events) {
+ for (Event event : events) {
printEvent(pw, event, prettyDates);
}
pw.decreaseIndent();
@@ -772,9 +772,17 @@ class UserUsageStatsService {
continue;
}
pw.printPair("package", usageStats.mPackageName);
- pw.printPair("totalTime",
+ pw.printPair("totalTimeUsed",
formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
- pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
+ pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
+ pw.printPair("totalTimeVisible",
+ formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates));
+ pw.printPair("lastTimeVisible",
+ formatDateTime(usageStats.mLastTimeVisible, prettyDates));
+ pw.printPair("totalTimeFS",
+ formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates));
+ pw.printPair("lastTimeFS",
+ formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates));
pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
pw.println();
}
@@ -845,7 +853,7 @@ class UserUsageStatsService {
final EventList events = stats.events;
final int eventCount = events != null ? events.size() : 0;
for (int i = 0; i < eventCount; i++) {
- final UsageEvents.Event event = events.get(i);
+ final Event event = events.get(i);
if (pkg != null && !pkg.equals(event.mPackage)) {
continue;
}
@@ -858,13 +866,13 @@ class UserUsageStatsService {
private static String intervalToString(int interval) {
switch (interval) {
- case UsageStatsManager.INTERVAL_DAILY:
+ case INTERVAL_DAILY:
return "daily";
- case UsageStatsManager.INTERVAL_WEEKLY:
+ case INTERVAL_WEEKLY:
return "weekly";
- case UsageStatsManager.INTERVAL_MONTHLY:
+ case INTERVAL_MONTHLY:
return "monthly";
- case UsageStatsManager.INTERVAL_YEARLY:
+ case INTERVAL_YEARLY:
return "yearly";
default:
return "?";
@@ -873,47 +881,49 @@ class UserUsageStatsService {
private static String eventToString(int eventType) {
switch (eventType) {
- case UsageEvents.Event.NONE:
+ case Event.NONE:
return "NONE";
- case UsageEvents.Event.MOVE_TO_BACKGROUND:
- return "MOVE_TO_BACKGROUND";
- case UsageEvents.Event.MOVE_TO_FOREGROUND:
- return "MOVE_TO_FOREGROUND";
- case UsageEvents.Event.FOREGROUND_SERVICE_START:
+ case Event.ACTIVITY_PAUSED:
+ return "ACTIVITY_PAUSED";
+ case Event.ACTIVITY_RESUMED:
+ return "ACTIVITY_RESUMED";
+ case Event.FOREGROUND_SERVICE_START:
return "FOREGROUND_SERVICE_START";
- case UsageEvents.Event.FOREGROUND_SERVICE_STOP:
+ case Event.FOREGROUND_SERVICE_STOP:
return "FOREGROUND_SERVICE_STOP";
- case UsageEvents.Event.END_OF_DAY:
+ case Event.ACTIVITY_STOPPED:
+ return "ACTIVITY_STOPPED";
+ case Event.END_OF_DAY:
return "END_OF_DAY";
- case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE:
+ case Event.ROLLOVER_FOREGROUND_SERVICE:
return "ROLLOVER_FOREGROUND_SERVICE";
- case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
+ case Event.CONTINUE_PREVIOUS_DAY:
return "CONTINUE_PREVIOUS_DAY";
- case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE:
+ case Event.CONTINUING_FOREGROUND_SERVICE:
return "CONTINUING_FOREGROUND_SERVICE";
- case UsageEvents.Event.CONFIGURATION_CHANGE:
+ case Event.CONFIGURATION_CHANGE:
return "CONFIGURATION_CHANGE";
- case UsageEvents.Event.SYSTEM_INTERACTION:
+ case Event.SYSTEM_INTERACTION:
return "SYSTEM_INTERACTION";
- case UsageEvents.Event.USER_INTERACTION:
+ case Event.USER_INTERACTION:
return "USER_INTERACTION";
- case UsageEvents.Event.SHORTCUT_INVOCATION:
+ case Event.SHORTCUT_INVOCATION:
return "SHORTCUT_INVOCATION";
- case UsageEvents.Event.CHOOSER_ACTION:
+ case Event.CHOOSER_ACTION:
return "CHOOSER_ACTION";
- case UsageEvents.Event.NOTIFICATION_SEEN:
+ case Event.NOTIFICATION_SEEN:
return "NOTIFICATION_SEEN";
- case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+ case Event.STANDBY_BUCKET_CHANGED:
return "STANDBY_BUCKET_CHANGED";
- case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+ case Event.NOTIFICATION_INTERRUPTION:
return "NOTIFICATION_INTERRUPTION";
- case UsageEvents.Event.SLICE_PINNED:
+ case Event.SLICE_PINNED:
return "SLICE_PINNED";
- case UsageEvents.Event.SLICE_PINNED_PRIV:
+ case Event.SLICE_PINNED_PRIV:
return "SLICE_PINNED_PRIV";
- case UsageEvents.Event.SCREEN_INTERACTIVE:
+ case Event.SCREEN_INTERACTIVE:
return "SCREEN_INTERACTIVE";
- case UsageEvents.Event.SCREEN_NON_INTERACTIVE:
+ case Event.SCREEN_NON_INTERACTIVE:
return "SCREEN_NON_INTERACTIVE";
case UsageEvents.Event.KEYGUARD_SHOWN:
return "KEYGUARD_SHOWN";
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e7ce78a7380c..fb371c176352 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -460,6 +460,12 @@ public class TelecomManager {
"android.telecom.extra.START_CALL_WITH_RTT";
/**
+ * A boolean extra set to indicate whether an app is eligible to be bound to when there are
+ * ongoing calls on the device.
+ */
+ public static final String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED";
+
+ /**
* A boolean meta-data value indicating whether an {@link InCallService} implements an
* in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which
* would also like to replace the in-call interface should set this meta-data to {@code true} in
@@ -470,9 +476,7 @@ public class TelecomManager {
/**
* A boolean meta-data value indicating whether an {@link InCallService} implements an
* in-call user interface to be used while the device is in car-mode (see
- * {@link android.content.res.Configuration.UI_MODE_TYPE_CAR}).
- *
- * @hide
+ * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
*/
public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI =
"android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
@@ -2038,7 +2042,6 @@ public class TelecomManager {
} catch (RemoteException e) {
Log.e(TAG, "RemoteException handleCallIntent: " + e);
}
-
}
private ITelecomService getTelecomService() {
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 6a396ce33093..1cbe5a26caed 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3640,8 +3640,9 @@ public final class Telephony {
/**
* Generates a content {@link Uri} used to receive updates on precise carrier identity
- * change on the given subscriptionId
- * {@link TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED}.
+ * change on the given subscriptionId returned by
+ * {@link TelephonyManager#getSimPreciseCarrierId()}.
+ * @see TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED
* <p>
* Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
* precise carrier identity {@link TelephonyManager#getSimPreciseCarrierId()}
@@ -3652,7 +3653,6 @@ public final class Telephony {
*
* @param subscriptionId the subscriptionId to receive updates on
* @return the Uri used to observe precise carrier identity changes
- * @hide
*/
public static Uri getPreciseCarrierIdUriForSubscriptionId(int subscriptionId) {
return Uri.withAppendedPath(Uri.withAppendedPath(CONTENT_URI, "precise"),
@@ -3674,22 +3674,20 @@ public final class Telephony {
public static final String CARRIER_ID = "carrier_id";
/**
- * A user facing carrier name for precise carrier id.
- * @see TelephonyManager#getSimPreciseCarrierIdName()
+ * A fine-grained carrier id.
+ * @see TelephonyManager#getSimPreciseCarrierId()
* This is not a database column, only used to notify content observers for
* {@link #getPreciseCarrierIdUriForSubscriptionId(int)}
- * @hide
*/
- public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name";
+ public static final String PRECISE_CARRIER_ID = "precise_carrier_id";
/**
- * A fine-grained carrier id.
- * @see TelephonyManager#getSimPreciseCarrierId()
+ * A user facing carrier name for precise carrier id {@link #PRECISE_CARRIER_ID}.
+ * @see TelephonyManager#getSimPreciseCarrierIdName()
* This is not a database column, only used to notify content observers for
* {@link #getPreciseCarrierIdUriForSubscriptionId(int)}
- * @hide
*/
- public static final String PRECISE_CARRIER_ID = "precise_carrier_id";
+ public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name";
/**
* A unique parent carrier id. The parent-child
@@ -3703,18 +3701,6 @@ public final class Telephony {
public static final String PARENT_CARRIER_ID = "parent_carrier_id";
/**
- * A unique mno carrier id. mno carrier shares the same {@link All#MCCMNC} as carrier id
- * and can be solely identified by {@link All#MCCMNC} only. If there is no such mno
- * carrier, then mno carrier id equals to {@link #CARRIER_ID carrier id}.
- *
- * <p>mno carrier id can be used as fallback id. When the exact carrier id configurations
- * are not found, usually fall back to its mno carrier id.
- * <P>Type: INTEGER </P>
- * @hide
- */
- public static final String MNO_CARRIER_ID = "mno_carrier_id";
-
- /**
* Contains mappings between matching rules with carrier id for all carriers.
* @hide
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 17f32616027f..388b5fb43096 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -68,7 +68,13 @@ public class CarrierConfigManager {
* This intent is broadcast by the system when carrier config changes. An int is specified in
* {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra
* {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid
- * one is available for the slot index.
+ * one is available for the slot index. An optional int extra
+ * {@link TelephonyManager#EXTRA_CARRIER_ID} is included to indicate the carrier id for the
+ * changed carrier configuration. An optional int extra
+ * {@link TelephonyManager#EXTRA_PRECISE_CARRIER_ID} is included to indicate the precise
+ * carrier id for the changed carrier configuration.
+ * @see TelephonyManager#getSimCarrierId()
+ * @see TelephonyManager#getSimPreciseCarrierId()
*/
public static final String
ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
@@ -81,7 +87,6 @@ public class CarrierConfigManager {
* Specifies a value that identifies the version of the carrier configuration that is
* currently in use. This string is displayed on the UI.
* The format of the string is not specified.
- * @hide
*/
public static final String KEY_CARRIER_CONFIG_VERSION_STRING =
"carrier_config_version_string";
@@ -1393,9 +1398,9 @@ public class CarrierConfigManager {
* Example: "default"
*
* {@code ERROR_CODE_1} is an integer defined in
- * {@link com.android.internal.telephony.dataconnection.DcFailCause DcFailure}
+ * {@link DataFailCause DcFailure}
* Example:
- * {@link com.android.internal.telephony.dataconnection.DcFailCause#MISSING_UNKNOWN_APN}
+ * {@link DataFailCause#MISSING_UNKNOWN_APN}
*
* {@code CARRIER_ACTION_IDX_1} is an integer defined in
* {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
new file mode 100644
index 000000000000..c6f7d0e458db
--- /dev/null
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Returned as the reason for a connection failure as defined
+ * by RIL_DataCallFailCause in ril.h and some local errors.
+ * @hide
+ */
+public enum DataFailCause {
+ NONE(0),
+
+ // This series of errors as specified by the standards
+ // specified in ril.h
+ OPERATOR_BARRED(0x08), /* no retry */
+ NAS_SIGNALLING(0x0E),
+ LLC_SNDCP(0x19),
+ INSUFFICIENT_RESOURCES(0x1A),
+ MISSING_UNKNOWN_APN(0x1B), /* no retry */
+ UNKNOWN_PDP_ADDRESS_TYPE(0x1C), /* no retry */
+ USER_AUTHENTICATION(0x1D), /* no retry */
+ ACTIVATION_REJECT_GGSN(0x1E), /* no retry */
+ ACTIVATION_REJECT_UNSPECIFIED(0x1F),
+ SERVICE_OPTION_NOT_SUPPORTED(0x20), /* no retry */
+ SERVICE_OPTION_NOT_SUBSCRIBED(0x21), /* no retry */
+ SERVICE_OPTION_OUT_OF_ORDER(0x22),
+ NSAPI_IN_USE(0x23), /* no retry */
+ REGULAR_DEACTIVATION(0x24), /* possibly restart radio, based on config */
+ QOS_NOT_ACCEPTED(0x25),
+ NETWORK_FAILURE(0x26),
+ UMTS_REACTIVATION_REQ(0x27),
+ FEATURE_NOT_SUPP(0x28),
+ TFT_SEMANTIC_ERROR(0x29),
+ TFT_SYTAX_ERROR(0x2A),
+ UNKNOWN_PDP_CONTEXT(0x2B),
+ FILTER_SEMANTIC_ERROR(0x2C),
+ FILTER_SYTAX_ERROR(0x2D),
+ PDP_WITHOUT_ACTIVE_TFT(0x2E),
+ ONLY_IPV4_ALLOWED(0x32), /* no retry */
+ ONLY_IPV6_ALLOWED(0x33), /* no retry */
+ ONLY_SINGLE_BEARER_ALLOWED(0x34),
+ ESM_INFO_NOT_RECEIVED(0x35),
+ PDN_CONN_DOES_NOT_EXIST(0x36),
+ MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED(0x37),
+ MAX_ACTIVE_PDP_CONTEXT_REACHED(0x41),
+ UNSUPPORTED_APN_IN_CURRENT_PLMN(0x42),
+ INVALID_TRANSACTION_ID(0x51),
+ MESSAGE_INCORRECT_SEMANTIC(0x5F),
+ INVALID_MANDATORY_INFO(0x60),
+ MESSAGE_TYPE_UNSUPPORTED(0x61),
+ MSG_TYPE_NONCOMPATIBLE_STATE(0x62),
+ UNKNOWN_INFO_ELEMENT(0x63),
+ CONDITIONAL_IE_ERROR(0x64),
+ MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE(0x65),
+ PROTOCOL_ERRORS(0x6F), /* no retry */
+ APN_TYPE_CONFLICT(0x70),
+ INVALID_PCSCF_ADDR(0x71),
+ INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN(0x72),
+ EMM_ACCESS_BARRED(0x73),
+ EMERGENCY_IFACE_ONLY(0x74),
+ IFACE_MISMATCH(0x75),
+ COMPANION_IFACE_IN_USE(0x76),
+ IP_ADDRESS_MISMATCH(0x77),
+ IFACE_AND_POL_FAMILY_MISMATCH(0x78),
+ EMM_ACCESS_BARRED_INFINITE_RETRY(0x79),
+ AUTH_FAILURE_ON_EMERGENCY_CALL(0x7A),
+
+ // OEM sepecific error codes. To be used by OEMs when they don't
+ // want to reveal error code which would be replaced by ERROR_UNSPECIFIED
+ OEM_DCFAILCAUSE_1(0x1001),
+ OEM_DCFAILCAUSE_2(0x1002),
+ OEM_DCFAILCAUSE_3(0x1003),
+ OEM_DCFAILCAUSE_4(0x1004),
+ OEM_DCFAILCAUSE_5(0x1005),
+ OEM_DCFAILCAUSE_6(0x1006),
+ OEM_DCFAILCAUSE_7(0x1007),
+ OEM_DCFAILCAUSE_8(0x1008),
+ OEM_DCFAILCAUSE_9(0x1009),
+ OEM_DCFAILCAUSE_10(0x100A),
+ OEM_DCFAILCAUSE_11(0x100B),
+ OEM_DCFAILCAUSE_12(0x100C),
+ OEM_DCFAILCAUSE_13(0x100D),
+ OEM_DCFAILCAUSE_14(0x100E),
+ OEM_DCFAILCAUSE_15(0x100F),
+
+ // Local errors generated by Vendor RIL
+ // specified in ril.h
+ REGISTRATION_FAIL(-1),
+ GPRS_REGISTRATION_FAIL(-2),
+ SIGNAL_LOST(-3), /* no retry */
+ PREF_RADIO_TECH_CHANGED(-4),
+ RADIO_POWER_OFF(-5), /* no retry */
+ TETHERED_CALL_ACTIVE(-6), /* no retry */
+ ERROR_UNSPECIFIED(0xFFFF),
+
+ // Errors generated by the Framework
+ // specified here
+ UNKNOWN(0x10000),
+ RADIO_NOT_AVAILABLE(0x10001), /* no retry */
+ UNACCEPTABLE_NETWORK_PARAMETER(0x10002), /* no retry */
+ CONNECTION_TO_DATACONNECTIONAC_BROKEN(0x10003),
+ LOST_CONNECTION(0x10004),
+ RESET_BY_FRAMEWORK(0x10005);
+
+ private final int mErrorCode;
+ private static final HashMap<Integer, DataFailCause> sErrorCodeToFailCauseMap;
+ static {
+ sErrorCodeToFailCauseMap = new HashMap<Integer, DataFailCause>();
+ for (DataFailCause fc : values()) {
+ sErrorCodeToFailCauseMap.put(fc.getErrorCode(), fc);
+ }
+ }
+
+ /**
+ * Map of subId -> set of data call setup permanent failure for the carrier.
+ */
+ private static final HashMap<Integer, HashSet<DataFailCause>> sPermanentFailureCache =
+ new HashMap<>();
+
+ DataFailCause(int errorCode) {
+ mErrorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Returns whether or not the fail cause is a failure that requires a modem restart
+ *
+ * @param context device context
+ * @param subId subscription index
+ * @return true if the fail cause code needs platform to trigger a modem restart.
+ */
+ public boolean isRadioRestartFailure(Context context, int subId) {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(subId);
+
+ if (b != null) {
+ if (this == REGULAR_DEACTIVATION
+ && b.getBoolean(CarrierConfigManager
+ .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL)) {
+ // This is for backward compatibility support. We need to continue support this
+ // old configuration until it gets removed in the future.
+ return true;
+ }
+ // Check the current configurations.
+ int[] causeCodes = b.getIntArray(CarrierConfigManager
+ .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY);
+ if (causeCodes != null) {
+ return Arrays.stream(causeCodes).anyMatch(i -> i == getErrorCode());
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isPermanentFailure(Context context, int subId) {
+
+ synchronized (sPermanentFailureCache) {
+
+ HashSet<DataFailCause> permanentFailureSet = sPermanentFailureCache.get(subId);
+
+ // In case of cache miss, we need to look up the settings from carrier config.
+ if (permanentFailureSet == null) {
+ // Retrieve the permanent failure from carrier config
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(subId);
+ if (b != null) {
+ String[] permanentFailureStrings = b.getStringArray(CarrierConfigManager.
+ KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS);
+
+ if (permanentFailureStrings != null) {
+ permanentFailureSet = new HashSet<>();
+ for (String failure : permanentFailureStrings) {
+ permanentFailureSet.add(DataFailCause.valueOf(failure));
+ }
+ }
+ }
+ }
+
+ // If we are not able to find the configuration from carrier config, use the default
+ // ones.
+ if (permanentFailureSet == null) {
+ permanentFailureSet = new HashSet<DataFailCause>() {
+ {
+ add(OPERATOR_BARRED);
+ add(MISSING_UNKNOWN_APN);
+ add(UNKNOWN_PDP_ADDRESS_TYPE);
+ add(USER_AUTHENTICATION);
+ add(ACTIVATION_REJECT_GGSN);
+ add(SERVICE_OPTION_NOT_SUPPORTED);
+ add(SERVICE_OPTION_NOT_SUBSCRIBED);
+ add(NSAPI_IN_USE);
+ add(ONLY_IPV4_ALLOWED);
+ add(ONLY_IPV6_ALLOWED);
+ add(PROTOCOL_ERRORS);
+ add(RADIO_POWER_OFF);
+ add(TETHERED_CALL_ACTIVE);
+ add(RADIO_NOT_AVAILABLE);
+ add(UNACCEPTABLE_NETWORK_PARAMETER);
+ add(SIGNAL_LOST);
+ }
+ };
+ }
+
+ sPermanentFailureCache.put(subId, permanentFailureSet);
+ }
+
+ return permanentFailureSet.contains(this);
+ }
+ }
+
+ public boolean isEventLoggable() {
+ return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) ||
+ (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||
+ (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) ||
+ (this == SERVICE_OPTION_NOT_SUBSCRIBED) ||
+ (this == SERVICE_OPTION_NOT_SUPPORTED) ||
+ (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) ||
+ (this == ONLY_IPV4_ALLOWED) || (this == ONLY_IPV6_ALLOWED) ||
+ (this == PROTOCOL_ERRORS) || (this == SIGNAL_LOST) ||
+ (this == RADIO_POWER_OFF) || (this == TETHERED_CALL_ACTIVE) ||
+ (this == UNACCEPTABLE_NETWORK_PARAMETER);
+ }
+
+ public static DataFailCause fromInt(int errorCode) {
+ DataFailCause fc = sErrorCodeToFailCauseMap.get(errorCode);
+ if (fc == null) {
+ fc = UNKNOWN;
+ }
+ return fc;
+ }
+}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c40eb9ac91e3..387453fa3985 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2403,7 +2403,7 @@ public class SubscriptionManager {
*
* Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
* permission or had carrier privilege permission on the subscriptions:
- * {@link TelephonyManager#hasCarrierPrivileges(int)} or
+ * {@link TelephonyManager#hasCarrierPrivileges()} or
* {@link #canManageSubscription(SubscriptionInfo)}
*
* @throws SecurityException if the caller doesn't meet the requirements
@@ -2441,7 +2441,7 @@ public class SubscriptionManager {
*
* Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
* permission or had carrier privilege permission on the subscriptions:
- * {@link TelephonyManager#hasCarrierPrivileges(int)} or
+ * {@link TelephonyManager#hasCarrierPrivileges()} or
* {@link #canManageSubscription(SubscriptionInfo)}
*
* @throws SecurityException if the caller doesn't meet the requirements
@@ -2477,7 +2477,7 @@ public class SubscriptionManager {
*
* Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE}
* permission or had carrier privilege permission on the subscription.
- * {@link TelephonyManager#hasCarrierPrivileges(int)}
+ * {@link TelephonyManager#hasCarrierPrivileges()}
*
* @throws SecurityException if the caller doesn't meet the requirements
* outlined above.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 33f856726791..348ab2a38716 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -225,6 +225,13 @@ public class TelephonyManager {
@SystemApi
public static final int SRVCC_STATE_HANDOVER_CANCELED = 3;
+ /**
+ * An invalid card identifier.
+ * @hide
+ */
+ @SystemApi
+ public static final int INVALID_CARD_ID = -1;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"SRVCC_STATE_"},
@@ -1217,81 +1224,79 @@ public class TelephonyManager {
"android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED";
/**
+ * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates
+ * the updated carrier id returned by {@link TelephonyManager#getSimCarrierId()}.
+ * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
+ * the carrier cannot be identified.
+ */
+ public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID";
+
+ /**
+ * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which
+ * indicates the updated carrier name of the current subscription.
+ * @see TelephonyManager#getSimCarrierIdName()
+ * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID},
+ * usually the brand name of the subsidiary (e.g. T-Mobile).
+ */
+ public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
+
+ /**
* Broadcast Action: The subscription precise carrier identity has changed.
- * Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be sent
- * on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}. However, its possible
- * that precise carrier identity changes while
- * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, the same
- * subscription switches to different IMSI could potentially change its precise carrier id.
+ * The precise carrier id can be used to further differentiate a carrier by different
+ * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique
+ * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id.
+ * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM,
+ * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based
+ * on the current subscription IMSI. For carriers without any fine-grained ids, precise carrier
+ * id is same as carrier id.
+ *
+ * <p>Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be
+ * sent on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} while its also
+ * possible to be sent without {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} when
+ * precise carrier id changes with the same carrier id.
+ * e.g, the same subscription switches to different IMSI could potentially change its
+ * precise carrier id while carrier id remains the same.
+ * @see #getSimPreciseCarrierId()
+ * @see #getSimCarrierId()
*
* The intent will have the following extra values:
* <ul>
* <li>{@link #EXTRA_PRECISE_CARRIER_ID} The up-to-date precise carrier id of the
* current subscription.
* </li>
- * <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date carrier name of the current
- * subscription.
+ * <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date name of the precise carrier id.
* </li>
* <li>{@link #EXTRA_SUBSCRIPTION_ID} The subscription id associated with the changed carrier
* identity.
* </li>
* </ul>
* <p class="note">This is a protected intent that can only be sent by the system.
- * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED =
"android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED";
/**
- * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates
- * the updated carrier id {@link TelephonyManager#getSimCarrierId()} of
- * the current subscription.
- * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
- * the carrier cannot be identified.
- */
- public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID";
-
- /**
- * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates
- * the updated mno carrier id of the current subscription.
- * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
- * the carrier cannot be identified.
- *
- *@hide
- */
- public static final String EXTRA_MNO_CARRIER_ID = "android.telephony.extra.MNO_CARRIER_ID";
-
- /**
- * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which
- * indicates the updated carrier name of the current subscription.
- * {@see TelephonyManager#getSimCarrierIdName()}
- * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID},
- * usually the brand name of the subsidiary (e.g. T-Mobile).
- */
- public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
-
- /**
* An int extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which
- * indicates the updated precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()} of
- * the current subscription. Note, its possible precise carrier id changes while
- * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, when
- * subscription switch to different IMSI.
+ * indicates the updated precise carrier id returned by
+ * {@link TelephonyManager#getSimPreciseCarrierId()}. Note, its possible precise carrier id
+ * changes while {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same
+ * e.g, when subscription switch to different IMSIs.
* <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
* the carrier cannot be identified.
- * @hide
*/
public static final String EXTRA_PRECISE_CARRIER_ID =
"android.telephony.extra.PRECISE_CARRIER_ID";
/**
* An string extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which
- * indicates the updated precise carrier name of the current subscription.
- * {@see TelephonyManager#getSimPreciseCarrierIdName()}
- * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID},
- * @hide
+ * indicates the updated precise carrier name returned by
+ * {@link TelephonyManager#getSimPreciseCarrierIdName()}.
+ * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID}, e.g,
+ * Tracfone-AT&T.
*/
- public static final String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
+ public static final String EXTRA_PRECISE_CARRIER_NAME =
+ "android.telephony.extra.PRECISE_CARRIER_NAME";
/**
* An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} to indicate the
@@ -3143,6 +3148,34 @@ public class TelephonyManager {
}
/**
+ * Get the card ID of the default eUICC card. If there is no eUICC, returns
+ * {@link #INVALID_CARD_ID}.
+ *
+ * <p>The card ID is a unique identifier associated with a UICC or eUICC card. Card IDs are
+ * unique to a device, and always refer to the same UICC or eUICC card unless the device goes
+ * through a factory reset.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @return card ID of the default eUICC card.
+ * @hide
+ */
+ @SystemApi
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public int getCardIdForDefaultEuicc() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ return INVALID_CARD_ID;
+ }
+ return telephony.getCardIdForDefaultEuicc(mSubId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ return INVALID_CARD_ID;
+ }
+ }
+
+ /**
* Gets all the UICC slots. The objects in the array can be null if the slot info is not
* available, which is possible between phone process starting and getting slot info from modem.
*
@@ -8518,7 +8551,7 @@ public class TelephonyManager {
/**
* Returns carrier id name of the current subscription.
- * <p>Carrier id name is a user-facing name of carrier id
+ * <p>Carrier id name is a user-facing name of carrier id returned by
* {@link #getSimCarrierId()}, usually the brand name of the subsidiary
* (e.g. T-Mobile). Each carrier could configure multiple {@link #getSimOperatorName() SPN} but
* should have a single carrier name. Carrier name is not a canonical identity,
@@ -8528,7 +8561,7 @@ public class TelephonyManager {
* @return Carrier name of the current subscription. Return {@code null} if the subscription is
* unavailable or the carrier cannot be identified.
*/
- public CharSequence getSimCarrierIdName() {
+ public @Nullable CharSequence getSimCarrierIdName() {
try {
ITelephony service = getITelephony();
if (service != null) {
@@ -8545,10 +8578,10 @@ public class TelephonyManager {
*
* <p>The precise carrier id can be used to further differentiate a carrier by different
* networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique
- * carrier id {@link #getSimCarrierId()} but can have multiple precise carrier id. e.g,
- * {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, while
- * {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based on the
- * current subscription IMSI.
+ * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id.
+ * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM,
+ * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based
+ * on the current subscription IMSI.
*
* <p>For carriers without any fine-grained carrier ids, return {@link #getSimCarrierId()}
* <p>Precise carrier ids are defined in the same way as carrier id
@@ -8558,8 +8591,6 @@ public class TelephonyManager {
* @return Returns fine-grained carrier id of the current subscription.
* Return {@link #UNKNOWN_CARRIER_ID} if the subscription is unavailable or the carrier cannot
* be identified.
- *
- * @hide
*/
public int getSimPreciseCarrierId() {
try {
@@ -8575,16 +8606,14 @@ public class TelephonyManager {
/**
* Similar like {@link #getSimCarrierIdName()}, returns user-facing name of the
- * precise carrier id {@link #getSimPreciseCarrierId()}
+ * precise carrier id returned by {@link #getSimPreciseCarrierId()}.
*
* <p>The returned name is unlocalized.
*
* @return user-facing name of the subscription precise carrier id. Return {@code null} if the
* subscription is unavailable or the carrier cannot be identified.
- *
- * @hide
*/
- public CharSequence getSimPreciseCarrierIdName() {
+ public @Nullable CharSequence getSimPreciseCarrierIdName() {
try {
ITelephony service = getITelephony();
if (service != null) {
@@ -8597,43 +8626,54 @@ public class TelephonyManager {
}
/**
- * Return a list of certs in hex string from loaded carrier privileges access rules.
+ * Returns carrier id based on sim MCCMNC (returned by {@link #getSimOperator()}) only.
+ * This is used for fallback when configurations/logic for exact carrier id
+ * {@link #getSimCarrierId()} are not found.
*
- * @return a list of certificate in hex string. return {@code null} if there is no certs
- * or privilege rules are not loaded yet.
+ * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+ * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
+ * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
+ * by default. After carrier id table update, a new carrier id was assigned. If apps don't
+ * take the update with the new id, it might be helpful to always fallback by using carrier
+ * id based on MCCMNC if there is no match.
*
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
- * @hide
+ * @return matching carrier id from sim MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the
+ * subscription is unavailable or the carrier cannot be identified.
*/
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public List<String> getCertsFromCarrierPrivilegeAccessRules() {
+ public int getCarrierIdFromSimMccMnc() {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getCertsFromCarrierPrivilegeAccessRules(getSubId());
+ return service.getCarrierIdFromMccMnc(getSlotIndex(), getSimOperator(), true);
}
} catch (RemoteException ex) {
// This could happen if binder process crashes.
}
- return null;
+ return UNKNOWN_CARRIER_ID;
}
- /**
- * Returns MNO carrier id of the current subscription’s MCCMNC.
- * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used
- * for MNO fallback when exact carrier id {@link #getSimCarrierId()}
- * configurations are not found.
- *
- * @return MNO carrier id of the current subscription. Return the value same as carrier id
- * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified.
- * @hide
- */
- public int getSimMNOCarrierId() {
+ /**
+ * Returns carrier id based on MCCMNC (returned by {@link #getSimOperator()}) only. This is
+ * used for fallback when configurations/logic for exact carrier id {@link #getSimCarrierId()}
+ * are not found.
+ *
+ * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+ * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
+ * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
+ * by default. After carrier id table update, a new carrier id was assigned. If apps don't
+ * take the update with the new id, it might be helpful to always fallback by using carrier
+ * id based on MCCMNC if there is no match.
+ *
+ * @return matching carrier id from passing MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the
+ * subscription is unavailable or the carrier cannot be identified.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getCarrierIdFromMccMnc(String mccmnc) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getSubscriptionMNOCarrierId(getSubId());
+ return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc, false);
}
} catch (RemoteException ex) {
// This could happen if binder process crashes.
@@ -8641,24 +8681,27 @@ public class TelephonyManager {
return UNKNOWN_CARRIER_ID;
}
- /**
- * Returns carrier id based on MCCMNC only. This is for fallback when exact carrier id
- * {@link #getSimCarrierId()} configurations are not found
- *
- * @return matching carrier id from passing mccmnc.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public int getCarrierIdFromMccMnc(String mccmnc) {
+ /**
+ * Return a list of certs in hex string from loaded carrier privileges access rules.
+ *
+ * @return a list of certificate in hex string. return {@code null} if there is no certs
+ * or privilege rules are not loaded yet.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public List<String> getCertsFromCarrierPrivilegeAccessRules() {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc);
+ return service.getCertsFromCarrierPrivilegeAccessRules(getSubId());
}
} catch (RemoteException ex) {
// This could happen if binder process crashes.
}
- return UNKNOWN_CARRIER_ID;
+ return null;
}
/**
diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java
new file mode 100644
index 000000000000..d50b516b8754
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsManager.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.ims;
+
+import android.annotation.SystemService;
+import android.content.Context;
+
+/**
+ * The manager class for RCS related utilities.
+ * @hide
+ */
+@SystemService(Context.TELEPHONY_RCS_SERVICE)
+public class RcsManager {
+
+ private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore();
+
+ /**
+ * Returns an instance of RcsMessageStore.
+ */
+ public RcsMessageStore getRcsMessageStore() {
+ return sRcsMessageStoreInstance;
+ }
+}
diff --git a/telephony/java/android/telephony/rcs/RcsManager.java b/telephony/java/android/telephony/ims/RcsMessageStore.java
index 0ef4e1552085..c89c0bebb1a1 100644
--- a/telephony/java/android/telephony/rcs/RcsManager.java
+++ b/telephony/java/android/telephony/ims/RcsMessageStore.java
@@ -14,24 +14,20 @@
* limitations under the License.
*/
-package android.telephony.rcs;
+package android.telephony.ims;
-import android.annotation.SystemService;
-import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.Rlog;
-
-import com.android.internal.telephony.rcs.IRcs;
+import android.telephony.ims.aidl.IRcs;
/**
- * RcsManager is the application interface to RcsProvider and provides access methods to
+ * RcsMessageStore is the application interface to RcsProvider and provides access methods to
* RCS related database tables.
* @hide - TODO make this public
*/
-@SystemService(Context.TELEPHONY_RCS_SERVICE)
-public class RcsManager {
- private static final String TAG = "RcsManager";
+public class RcsMessageStore {
+ private static final String TAG = "RcsMessageStore";
private static final boolean VDBG = false;
/**
diff --git a/telephony/java/android/telephony/ims/RcsThread.aidl b/telephony/java/android/telephony/ims/RcsThread.aidl
new file mode 100644
index 000000000000..79d473266272
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsThread.aidl
@@ -0,0 +1,20 @@
+/*
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+parcelable RcsThread; \ No newline at end of file
diff --git a/telephony/java/android/telephony/rcs/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java
index 83eb973ec12b..b7f440d94583 100644
--- a/telephony/java/android/telephony/rcs/RcsThread.java
+++ b/telephony/java/android/telephony/ims/RcsThread.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package android.telephony.rcs;
+package android.telephony.ims;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-
-import com.android.internal.telephony.rcs.IRcs;
+import android.telephony.ims.aidl.IRcs;
/**
* RcsThread represents a single RCS conversation thread. It holds messages that were sent and
diff --git a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
index 4c289acd15ef..b2e2fadca138 100644
--- a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.telephony.rcs;
+package android.telephony.ims.aidl;
+/**
+ * RPC definition between RCS storage APIs and phone process.
+ * {@hide}
+ */
interface IRcs {
- // RcsManager APIs
+ // RcsMessageStore APIs
void deleteThread(int threadId);
// RcsThread APIs
diff --git a/telephony/java/android/telephony/rcs/RcsThread.aidl b/telephony/java/android/telephony/rcs/RcsThread.aidl
deleted file mode 100644
index e2e0da5da347..000000000000
--- a/telephony/java/android/telephony/rcs/RcsThread.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
-**
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.telephony;
-
-parcelable RcsThread; \ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 88b9302afacb..46366d66a6eb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1337,18 +1337,6 @@ interface ITelephony {
String getSubscriptionCarrierName(int subId);
/**
- * Returns MNO carrier id of the current subscription’s MCCMNC.
- * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used
- * for MNO fallback when exact carrier id {@link #getSimCarrierId()}
- * configurations are not found.
- *
- * @return MNO carrier id of the current subscription. Return the value same as carrier id
- * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified.
- * @hide
- */
- int getSubscriptionMNOCarrierId(int subId);
-
- /**
* Returns fine-grained carrier id of the current subscription.
*
* <p>The precise carrier id can be used to further differentiate a carrier by different
@@ -1383,10 +1371,13 @@ interface ITelephony {
* Returns carrier id based on MCCMNC only. This will return a MNO carrier id used for fallback
* check when exact carrier id {@link #getSimCarrierId()} configurations are not found
*
+ * @param isSubscriptionMccMnc. If {@true} it means this is a query for subscription mccmnc
+ * {@false} otherwise.
+ *
* @return carrier id from passing mccmnc.
* @hide
*/
- int getCarrierIdFromMccMnc(int slotIndex, String mccmnc);
+ int getCarrierIdFromMccMnc(int slotIndex, String mccmnc, boolean isSubscriptionMccMnc);
/**
* Action set from carrier signalling broadcast receivers to enable/disable metered apns
@@ -1482,6 +1473,19 @@ interface ITelephony {
SignalStrength getSignalStrength(int subId);
/**
+ * Get the card ID of the default eUICC card. If there is no eUICC, returns
+ * {@link #INVALID_CARD_ID}.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @param subId subscription ID used for authentication
+ * @param callingPackage package making the call
+ * @return card ID of the default eUICC card.
+ * @hide
+ */
+ int getCardIdForDefaultEuicc(int subId, String callingPackage);
+
+ /**
* Get slot info for all the UICC slots.
* @return UiccSlotInfo array.
* @hide
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 4d765d3e5f3f..157609cec09c 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -37,8 +37,6 @@ java_sdk_library {
"junit.framework",
],
- droiddoc_options: ["-stubsourceonly"],
- metalava_enabled: false,
compile_dex: true,
}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 0a0d50cc330c..db5053eeb903 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -40,8 +40,6 @@ java_sdk_library {
"junit.textui",
],
- droiddoc_options: ["-stubsourceonly"],
- metalava_enabled: false,
compile_dex: true
}
diff --git a/test-runner/api/current.txt b/test-runner/api/current.txt
index 1170eb53ab7f..4ba1b8f2fdc1 100644
--- a/test-runner/api/current.txt
+++ b/test-runner/api/current.txt
@@ -125,8 +125,8 @@ package android.test {
method public static void assertEquals(double[], double[]);
method public static void assertEquals(java.lang.String, java.lang.Object[], java.lang.Object[]);
method public static void assertEquals(java.lang.Object[], java.lang.Object[]);
- method public static void assertEquals(java.lang.String, java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>);
- method public static void assertEquals(java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>);
+ method public static void assertEquals(java.lang.String, java.util.Set<?>, java.util.Set<?>);
+ method public static void assertEquals(java.util.Set<?>, java.util.Set<?>);
method public static java.util.regex.MatchResult assertMatchesRegex(java.lang.String, java.lang.String, java.lang.String);
method public static java.util.regex.MatchResult assertMatchesRegex(java.lang.String, java.lang.String);
method public static void assertNotContainsRegex(java.lang.String, java.lang.String, java.lang.String);
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d8b3b2086335..75ee0896c23a 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -18,20 +18,23 @@ package com.android.server.pm.dex;
import static com.google.common.truth.Truth.assertThat;
+import android.app.UiAutomation;
import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.util.EventLog;
+
import dalvik.system.DexClassLoader;
-import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
@@ -40,6 +43,7 @@ import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* Integration tests for {@link com.android.server.pm.dex.DexLogger}.
@@ -47,10 +51,10 @@ import java.util.List;
* The setup for the test dynamically loads code in a jar extracted
* from our assets (a secondary dex file).
*
- * We then use adb to trigger secondary dex file reconcilation (and
- * wait for it to complete). As a side-effect of this DexLogger should
- * be notified of the file and should log the hash of the file's name
- * and content. We verify that this message appears in the event log.
+ * We then use shell commands to trigger dynamic code logging (and wait
+ * for it to complete). This causes DexLogger to log the hash of the
+ * file's name and content. We verify that this message appears in
+ * the event log.
*
* Run with "atest DexLoggerIntegrationTests".
*/
@@ -58,29 +62,89 @@ import java.util.List;
@RunWith(JUnit4.class)
public final class DexLoggerIntegrationTests {
- private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest";
-
// Event log tag used for SNET related events
private static final int SNET_TAG = 0x534e4554;
+
// Subtag used to distinguish dynamic code loading events
private static final String DCL_SUBTAG = "dcl";
- // Obtained via "echo -n copied.jar | sha256sum"
- private static final String EXPECTED_NAME_HASH =
- "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+ // All the tags we care about
+ private static final int[] TAG_LIST = new int[] { SNET_TAG };
+
+ // This is {@code DynamicCodeLoggingService#JOB_ID}
+ private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028;
- private static String expectedContentHash;
+ private static Context sContext;
+ private static int sMyUid;
@BeforeClass
- public static void setUpAll() throws Exception {
- Context context = InstrumentationRegistry.getTargetContext();
+ public static void setUpAll() {
+ sContext = InstrumentationRegistry.getTargetContext();
+ sMyUid = android.os.Process.myUid();
+ }
+
+ @Before
+ public void primeEventLog() {
+ // Force a round trip to logd to make sure everything is up to date.
+ // Without this the first test passes and others don't - we don't see new events in the
+ // log. The exact reason is unclear.
+ EventLog.writeEvent(SNET_TAG, "Dummy event");
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents() throws Exception {
+ File privateCopyFile = fileForJar("copied.jar");
+ // Obtained via "echo -n copied.jar | sha256sum"
+ String expectedNameHash =
+ "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+ String expectedContentHash = copyAndHashJar(privateCopyFile);
+
+ // Feed the jar to a class loader and make sure it contains what we expect.
+ ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
+ ClassLoader loader =
+ new DexClassLoader(privateCopyFile.toString(), null, null, parentClassLoader);
+ loader.loadClass("com.android.dcl.Simple");
+
+ // And make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDexLogger();
+
+ assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+
+ public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception {
+ File privateCopyFile = fileForJar("copied2.jar");
+ String expectedNameHash =
+ "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
+ String expectedContentHash = copyAndHashJar(privateCopyFile);
+
+ // This time make sure an unknown class loader is an ancestor of the class loader we use.
+ ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
+ ClassLoader unknownClassLoader = new UnknownClassLoader(knownClassLoader);
+ ClassLoader loader =
+ new DexClassLoader(privateCopyFile.toString(), null, null, unknownClassLoader);
+ loader.loadClass("com.android.dcl.Simple");
+
+ // And make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDexLogger();
+
+ assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+ }
+
+ private static File fileForJar(String name) {
+ return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name);
+ }
+
+ private static String copyAndHashJar(File copyTo) throws Exception {
MessageDigest hasher = MessageDigest.getInstance("SHA-256");
// Copy the jar from our Java resources to a private data directory
- File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar");
Class<?> thisClass = DexLoggerIntegrationTests.class;
try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
- OutputStream output = new FileOutputStream(privateCopy)) {
+ OutputStream output = new FileOutputStream(copyTo)) {
byte[] buffer = new byte[1024];
while (true) {
int numRead = input.read(buffer);
@@ -92,42 +156,63 @@ public final class DexLoggerIntegrationTests {
}
}
- // Remember the SHA-256 of the file content to check that it is the same as
- // the value we see logged.
+ // Compute the SHA-256 of the file content so we can check that it is the same as the value
+ // we see logged.
Formatter formatter = new Formatter();
for (byte b : hasher.digest()) {
formatter.format("%02X", b);
}
- expectedContentHash = formatter.toString();
- // Feed the jar to a class loader and make sure it contains what we expect.
- ClassLoader loader =
- new DexClassLoader(
- privateCopy.toString(), null, null, context.getClass().getClassLoader());
- loader.loadClass("com.android.dcl.Simple");
+ return formatter.toString();
}
- @Test
- public void testDexLoggerReconcileGeneratesEvents() throws Exception {
- int[] tagList = new int[] { SNET_TAG };
+ private static long mostRecentEventTimeNanos() throws Exception {
List<EventLog.Event> events = new ArrayList<>();
- // There may already be events in the event log - figure out the most recent one
- EventLog.readEvents(tagList, events);
- long previousEventNanos =
- events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
- events.clear();
+ EventLog.readEvents(TAG_LIST, events);
+ return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+ }
- Process process = Runtime.getRuntime().exec(
- "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME);
- int exitCode = process.waitFor();
- assertThat(exitCode).isEqualTo(0);
+ private static void runDexLogger() throws Exception {
+ // This forces {@code DynamicCodeLoggingService} to start now.
+ runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+ // Wait for the job to have run.
+ long startTime = SystemClock.elapsedRealtime();
+ while (true) {
+ String response = runCommand(
+ "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+ if (!response.contains("pending") && !response.contains("active")) {
+ break;
+ }
+ if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
+ throw new AssertionError("Job has not completed: " + response);
+ }
+ SystemClock.sleep(100);
+ }
+ }
- int myUid = android.os.Process.myUid();
- String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash;
+ private static String runCommand(String command) throws Exception {
+ ByteArrayOutputStream response = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1000];
+ UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ ParcelFileDescriptor fd = ui.executeShellCommand(command);
+ try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+ while (true) {
+ int count = input.read(buffer);
+ if (count == -1) {
+ break;
+ }
+ response.write(buffer, 0, count);
+ }
+ }
+ return response.toString("UTF-8");
+ }
- EventLog.readEvents(tagList, events);
- boolean found = false;
+ private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash,
+ String expectedContentHash) throws Exception {
+ List<EventLog.Event> events = new ArrayList<>();
+ EventLog.readEvents(TAG_LIST, events);
+ int found = 0;
for (EventLog.Event event : events) {
if (event.getTimeNanos() <= previousEventNanos) {
continue;
@@ -140,15 +225,28 @@ public final class DexLoggerIntegrationTests {
continue;
}
int uid = (int) data[1];
- if (uid != myUid) {
+ if (uid != sMyUid) {
continue;
}
String message = (String) data[2];
- assertThat(message).isEqualTo(expectedMessage);
- found = true;
+ if (!message.startsWith(expectedNameHash)) {
+ continue;
+ }
+
+ assertThat(message).endsWith(expectedContentHash);
+ ++found;
}
- assertThat(found).isTrue();
+ assertThat(found).isEqualTo(1);
+ }
+
+ /**
+ * A class loader that does nothing useful, but importantly doesn't extend BaseDexClassLoader.
+ */
+ private static class UnknownClassLoader extends ClassLoader {
+ UnknownClassLoader(ClassLoader parent) {
+ super(parent);
+ }
}
}
diff --git a/tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java b/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
index 7f5f03e0d5a4..290e04ce8abb 100644
--- a/tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java
+++ b/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
@@ -16,17 +16,17 @@
package com.android.tests.rcs;
import android.support.test.runner.AndroidJUnit4;
-import android.telephony.rcs.RcsManager;
+import android.telephony.ims.RcsMessageStore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
-public class RcsManagerTest {
+public class RcsMessageStoreTest {
//TODO(sahinc): Add meaningful tests once we have more of the implementation in place
@Test
public void testDeleteThreadDoesntCrash() {
- RcsManager mRcsManager = new RcsManager();
- mRcsManager.deleteThread(0);
+ RcsMessageStore mRcsMessageStore = new RcsMessageStore();
+ mRcsMessageStore.deleteThread(0);
}
}
diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
index be74a6d162ae..7a5e7325ad22 100644
--- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
+++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
@@ -92,11 +92,11 @@ public class UsageStatsDatabasePerfTest {
event.mPackage = "fake.package.name" + pkg;
event.mClass = event.mPackage + ".class1";
event.mTimeStamp = 1;
- event.mEventType = UsageEvents.Event.MOVE_TO_FOREGROUND;
+ event.mEventType = UsageEvents.Event.ACTIVITY_RESUMED;
for (int evt = 0; evt < eventsPerPackage; evt++) {
intervalStats.events.insert(event);
intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp,
- event.mEventType);
+ event.mEventType, 1);
}
}
}
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
index 3480e96b3547..53afa26796ea 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
@@ -21,13 +21,14 @@ import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
-import androidx.collection.CircularArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
+import androidx.collection.CircularArray;
+
public class UsageLogActivity extends ListActivity implements Runnable {
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
@@ -155,10 +156,10 @@ public class UsageLogActivity extends ListActivity implements Runnable {
private String eventToString(int eventType) {
switch (eventType) {
- case UsageEvents.Event.MOVE_TO_FOREGROUND:
+ case UsageEvents.Event.ACTIVITY_RESUMED:
return "Foreground";
- case UsageEvents.Event.MOVE_TO_BACKGROUND:
+ case UsageEvents.Event.ACTIVITY_PAUSED:
return "Background";
case UsageEvents.Event.CONFIGURATION_CHANGE:
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 983802035bfb..436dd859beca 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -46,6 +46,7 @@ import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
import android.text.format.DateUtils;
+import android.util.Log;
import com.android.frameworks.tests.net.R;
import com.android.internal.util.HexDump;
import java.io.File;
@@ -89,6 +90,7 @@ public class ApfTest {
System.loadLibrary("frameworksnettestsjni");
}
+ private static final String TAG = "ApfTest";
// Expected return codes from APF interpreter.
private static final int PASS = 1;
private static final int DROP = 0;
@@ -869,6 +871,37 @@ public class ApfTest {
}
}
+ /**
+ * Generate APF program, run pcap file though APF filter, then check all the packets in the file
+ * should be dropped.
+ */
+ @Test
+ public void testApfFilterPcapFile() throws Exception {
+ final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
+ String pcapFilename = stageFile(R.raw.apfPcap);
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
+ config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ byte[] program = ipClientCallback.getApfProgram();
+ byte[] data = new byte[ApfFilter.Counter.totalSize()];
+ final boolean result;
+
+ result = dropsAllPackets(program, data, pcapFilename);
+ Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
+
+ assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
+ HexDump.toHexString(data, false), result);
+ }
+
private class MockIpClientCallback extends IpClient.Callback {
private final ConditionVariable mGotApfProgram = new ConditionVariable();
private byte[] mLastApfProgram;
@@ -1706,6 +1739,14 @@ public class ApfTest {
private native static boolean compareBpfApf(String filter, String pcap_filename,
byte[] apf_program);
+
+ /**
+ * Open packet capture file {@code pcapFilename} and run it through APF filter. Then
+ * checks whether all the packets are dropped and populates data[] {@code data} with
+ * the APF counters.
+ */
+ private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename);
+
@Test
public void testBroadcastAddress() throws Exception {
assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
diff --git a/tests/net/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp
index 1ea9e274ab9e..4222adf9e06b 100644
--- a/tests/net/jni/apf_jni.cpp
+++ b/tests/net/jni/apf_jni.cpp
@@ -21,37 +21,40 @@
#include <stdlib.h>
#include <string>
#include <utils/Log.h>
+#include <vector>
#include "apf_interpreter.h"
+#include "nativehelper/scoped_primitive_array.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
// JNI function acting as simply call-through to native APF interpreter.
static jint com_android_server_ApfTest_apfSimulate(
- JNIEnv* env, jclass, jbyteArray program, jbyteArray packet,
- jbyteArray data, jint filter_age) {
- uint8_t* program_raw = (uint8_t*)env->GetByteArrayElements(program, nullptr);
- uint8_t* packet_raw = (uint8_t*)env->GetByteArrayElements(packet, nullptr);
- uint8_t* data_raw = (uint8_t*)(data ? env->GetByteArrayElements(data, nullptr) : nullptr);
- uint32_t program_len = env->GetArrayLength(program);
- uint32_t packet_len = env->GetArrayLength(packet);
- uint32_t data_len = data ? env->GetArrayLength(data) : 0;
-
- // Merge program and data into a single buffer.
- uint8_t* program_and_data = (uint8_t*)malloc(program_len + data_len);
- memcpy(program_and_data, program_raw, program_len);
- memcpy(program_and_data + program_len, data_raw, data_len);
+ JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket,
+ jbyteArray jdata, jint filter_age) {
+
+ ScopedByteArrayRO packet(env, jpacket);
+ uint32_t packet_len = (uint32_t)packet.size();
+ uint32_t program_len = env->GetArrayLength(jprogram);
+ uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
+ std::vector<uint8_t> buf(program_len + data_len, 0);
+
+ env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data()));
+ if (jdata) {
+ // Merge program and data into a single buffer.
+ env->GetByteArrayRegion(jdata, 0, data_len,
+ reinterpret_cast<jbyte*>(buf.data() + program_len));
+ }
jint result =
- accept_packet(program_and_data, program_len, program_len + data_len,
- packet_raw, packet_len, filter_age);
- if (data) {
- memcpy(data_raw, program_and_data + program_len, data_len);
- env->ReleaseByteArrayElements(data, (jbyte*)data_raw, 0 /* copy back */);
- }
- free(program_and_data);
- env->ReleaseByteArrayElements(packet, (jbyte*)packet_raw, JNI_ABORT);
- env->ReleaseByteArrayElements(program, (jbyte*)program_raw, JNI_ABORT);
+ accept_packet(buf.data(), program_len, program_len + data_len,
+ reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age);
+
+ if (jdata) {
+ env->SetByteArrayRegion(jdata, 0, data_len,
+ reinterpret_cast<jbyte*>(buf.data() + program_len));
+ }
+
return result;
}
@@ -118,8 +121,7 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js
jstring jpcap_filename, jbyteArray japf_program) {
ScopedUtfChars filter(env, jfilter);
ScopedUtfChars pcap_filename(env, jpcap_filename);
- uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL);
- uint32_t apf_program_len = env->GetArrayLength(japf_program);
+ ScopedByteArrayRO apf_program(env, japf_program);
// Open pcap file for BPF filtering
ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
@@ -161,14 +163,15 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js
do {
apf_packet = pcap_next(apf_pcap.get(), &apf_header);
} while (apf_packet != NULL && !accept_packet(
- apf_program, apf_program_len, 0 /* data_len */,
+ reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())),
+ apf_program.size(), 0 /* data_len */,
apf_packet, apf_header.len, 0 /* filter_age */));
// Make sure both filters matched the same packet.
if (apf_packet == NULL && bpf_packet == NULL)
- break;
+ break;
if (apf_packet == NULL || bpf_packet == NULL)
- return false;
+ return false;
if (apf_header.len != bpf_header.len ||
apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
@@ -178,6 +181,48 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js
return true;
}
+static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram,
+ jbyteArray jdata, jstring jpcap_filename) {
+ ScopedUtfChars pcap_filename(env, jpcap_filename);
+ ScopedByteArrayRO apf_program(env, jprogram);
+ uint32_t apf_program_len = (uint32_t)apf_program.size();
+ uint32_t data_len = env->GetArrayLength(jdata);
+ pcap_pkthdr apf_header;
+ const uint8_t* apf_packet;
+ char pcap_error[PCAP_ERRBUF_SIZE];
+ std::vector<uint8_t> buf(apf_program_len + data_len, 0);
+
+ // Merge program and data into a single buffer.
+ env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data()));
+ env->GetByteArrayRegion(jdata, 0, data_len,
+ reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+
+ // Open pcap file
+ ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
+ ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
+
+ if (apf_pcap.get() == NULL) {
+ throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
+ return false;
+ }
+
+ while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) {
+ int result = accept_packet(buf.data(), apf_program_len,
+ apf_program_len + data_len, apf_packet, apf_header.len, 0);
+
+ // Return false once packet passes the filter
+ if (result) {
+ env->SetByteArrayRegion(jdata, 0, data_len,
+ reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+ return false;
+ }
+ }
+
+ env->SetByteArrayRegion(jdata, 0, data_len,
+ reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+ return true;
+}
+
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -192,6 +237,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
(void*)com_android_server_ApfTest_compileToBpf },
{ "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z",
(void*)com_android_server_ApfTest_compareBpfApf },
+ { "dropsAllPackets", "([B[BLjava/lang/String;)Z",
+ (void*)com_android_server_ApfTest_dropsAllPackets },
};
jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
diff --git a/tests/net/res/raw/apfPcap.pcap b/tests/net/res/raw/apfPcap.pcap
new file mode 100644
index 000000000000..6f69c4add0f8
--- /dev/null
+++ b/tests/net/res/raw/apfPcap.pcap
Binary files differ
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 583f14ac0cbd..9460c9e596e9 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -306,31 +306,6 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
break;
}
- for (size_t i = 0; i < entry->overlayable_declarations.size(); i++) {
- printer->Print((i == 0) ? " " : "|");
- printer->Print("OVERLAYABLE");
-
- if (entry->overlayable_declarations[i].policy) {
- switch (entry->overlayable_declarations[i].policy.value()) {
- case Overlayable::Policy::kProduct:
- printer->Print("_PRODUCT");
- break;
- case Overlayable::Policy::kProductServices:
- printer->Print("_PRODUCT_SERVICES");
- break;
- case Overlayable::Policy::kSystem:
- printer->Print("_SYSTEM");
- break;
- case Overlayable::Policy::kVendor:
- printer->Print("_VENDOR");
- break;
- case Overlayable::Policy::kPublic:
- printer->Print("_PUBLIC");
- break;
- }
- }
- }
-
printer->Println();
if (options.show_values) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4f25e0968c0e..95877045072b 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -99,7 +99,7 @@ struct ParsedResource {
ResourceId id;
Visibility::Level visibility_level = Visibility::Level::kUndefined;
bool allow_new = false;
- std::vector<Overlayable> overlayable_declarations;
+ Maybe<Overlayable> overlayable;
std::string comment;
std::unique_ptr<Value> value;
@@ -133,8 +133,8 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
}
}
- for (auto& overlayable : res->overlayable_declarations) {
- if (!table->AddOverlayable(res->name, overlayable, diag)) {
+ if (res->overlayable) {
+ if (!table->SetOverlayable(res->name, res->overlayable.value(), diag)) {
return false;
}
}
@@ -1063,20 +1063,19 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
<< "' for <overlayable> tag");
}
- std::string comment;
- std::vector<Overlayable::Policy> policies;
-
bool error = false;
+ std::string comment;
+ Overlayable::PolicyFlags current_policies = Overlayable::Policy::kNone;
const size_t start_depth = parser->depth();
while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
xml::XmlPullParser::Event event = parser->event();
if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
- // Break the loop when exiting the overyabale element
+ // Break the loop when exiting the overlayable element
break;
} else if (event == xml::XmlPullParser::Event::kEndElement
&& parser->depth() == start_depth + 1) {
// Clear the current policies when exiting the policy element
- policies.clear();
+ current_policies = Overlayable::Policy::kNone;
continue;
} else if (event == xml::XmlPullParser::Event::kComment) {
// Get the comment of individual item elements
@@ -1090,43 +1089,71 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
const Source item_source = source_.WithLine(parser->line_number());
const std::string& element_name = parser->element_name();
const std::string& element_namespace = parser->element_namespace();
-
if (element_namespace.empty() && element_name == "item") {
- if (!ParseOverlayableItem(parser, policies, comment, out_resource)) {
+ // Items specify the name and type of resource that should be overlayable
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(item_source)
+ << "<item> within an <overlayable> tag must have a 'name' attribute");
+ error = true;
+ continue;
+ }
+
+ Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+ if (!maybe_type) {
+ diag_->Error(DiagMessage(item_source)
+ << "<item> within an <overlayable> tag must have a 'type' attribute");
error = true;
+ continue;
+ }
+
+ const ResourceType* type = ParseResourceType(maybe_type.value());
+ if (type == nullptr) {
+ diag_->Error(DiagMessage(item_source)
+ << "invalid resource type '" << maybe_type.value()
+ << "' in <item> within an <overlayable>");
+ error = true;
+ continue;
}
+
+ ParsedResource child_resource;
+ child_resource.name.type = *type;
+ child_resource.name.entry = maybe_name.value().to_string();
+ child_resource.overlayable = Overlayable{current_policies, item_source, comment};
+ out_resource->child_resources.push_back(std::move(child_resource));
+
} else if (element_namespace.empty() && element_name == "policy") {
- if (!policies.empty()) {
+ if (current_policies != Overlayable::Policy::kNone) {
// If the policy list is not empty, then we are currently inside a policy element
diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested");
error = true;
break;
} else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
// Parse the polices separated by vertical bar characters to allow for specifying multiple
- // policies at once
+ // policies
for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
StringPiece trimmed_part = util::TrimWhitespace(part);
if (trimmed_part == "public") {
- policies.push_back(Overlayable::Policy::kPublic);
+ current_policies |= Overlayable::Policy::kPublic;
} else if (trimmed_part == "product") {
- policies.push_back(Overlayable::Policy::kProduct);
+ current_policies |= Overlayable::Policy::kProduct;
} else if (trimmed_part == "product_services") {
- policies.push_back(Overlayable::Policy::kProductServices);
+ current_policies |= Overlayable::Policy::kProductServices;
} else if (trimmed_part == "system") {
- policies.push_back(Overlayable::Policy::kSystem);
+ current_policies |= Overlayable::Policy::kSystem;
} else if (trimmed_part == "vendor") {
- policies.push_back(Overlayable::Policy::kVendor);
+ current_policies |= Overlayable::Policy::kVendor;
} else {
- diag_->Error(DiagMessage(out_resource->source)
- << "<policy> has unsupported type '" << trimmed_part << "'");
+ diag_->Error(DiagMessage(item_source)
+ << "<policy> has unsupported type '" << trimmed_part << "'");
error = true;
continue;
}
}
}
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
- diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> in "
- << " <overlayable>");
+ diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> "
+ << " in <overlayable>");
error = true;
break;
}
@@ -1135,61 +1162,6 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
return !error;
}
-bool ResourceParser::ParseOverlayableItem(xml::XmlPullParser* parser,
- const std::vector<Overlayable::Policy>& policies,
- const std::string& comment,
- ParsedResource* out_resource) {
- const Source item_source = source_.WithLine(parser->line_number());
-
- Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
- if (!maybe_name) {
- diag_->Error(DiagMessage(item_source)
- << "<item> within an <overlayable> tag must have a 'name' attribute");
- return false;
- }
-
- Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
- if (!maybe_type) {
- diag_->Error(DiagMessage(item_source)
- << "<item> within an <overlayable> tag must have a 'type' attribute");
- return false;
- }
-
- const ResourceType* type = ParseResourceType(maybe_type.value());
- if (type == nullptr) {
- diag_->Error(DiagMessage(out_resource->source)
- << "invalid resource type '" << maybe_type.value()
- << "' in <item> within an <overlayable>");
- return false;
- }
-
- ParsedResource child_resource;
- child_resource.name.type = *type;
- child_resource.name.entry = maybe_name.value().to_string();
- child_resource.source = item_source;
-
- if (policies.empty()) {
- Overlayable overlayable;
- overlayable.source = item_source;
- overlayable.comment = comment;
- child_resource.overlayable_declarations.push_back(overlayable);
- } else {
- for (Overlayable::Policy policy : policies) {
- Overlayable overlayable;
- overlayable.policy = policy;
- overlayable.source = item_source;
- overlayable.comment = comment;
- child_resource.overlayable_declarations.push_back(overlayable);
- }
- }
-
- if (options_.visibility) {
- child_resource.visibility_level = options_.visibility.value();
- }
- out_resource->child_resources.push_back(std::move(child_resource));
- return true;
-}
-
bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (ParseSymbolImpl(parser, out_resource)) {
out_resource->visibility_level = Visibility::Level::kUndefined;
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index ebacd6f1280e..06bb0c9cf264 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -96,10 +96,6 @@ class ResourceParser {
bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
- bool ParseOverlayableItem(xml::XmlPullParser* parser,
- const std::vector<Overlayable::Policy>& policies,
- const std::string& comment,
- ParsedResource* out_resource);
bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index c6f29ac53ca6..03e6197027cb 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -905,16 +905,16 @@ TEST_F(ResourceParserTest, ParseOverlayable) {
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ EXPECT_THAT(search_result.value().entry->overlayable.value().policies,
+ Eq(Overlayable::Policy::kNone));
search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ EXPECT_THAT(search_result.value().entry->overlayable.value().policies,
+ Eq(Overlayable::Policy::kNone));
}
TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
@@ -945,49 +945,44 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable& overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kNone));
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProduct));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct));
search_result = table_.FindResource(test::ParseNameOrDie("string/baz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProductServices));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProductServices));
search_result = table_.FindResource(test::ParseNameOrDie("string/fiz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kSystem));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kSystem));
search_result = table_.FindResource(test::ParseNameOrDie("string/fuz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kVendor));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor));
search_result = table_.FindResource(test::ParseNameOrDie("string/faz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kPublic));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kPublic));
}
TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) {
@@ -1031,22 +1026,18 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) {
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kVendor));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
- Eq(Overlayable::Policy::kProductServices));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable& overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor
+ | Overlayable::Policy::kProductServices));
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProduct));
- EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
- Eq(Overlayable::Policy::kSystem));
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct
+ | Overlayable::Policy::kSystem));
}
TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
@@ -1067,7 +1058,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
EXPECT_FALSE(TestParse(input));
input = R"(
- <overlayable">
+ <overlayable>
<policy type="product">
<item type="string" name="foo" />
<item type="string" name="foo" />
@@ -1080,45 +1071,30 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
<policy type="product">
<item type="string" name="foo" />
</policy>
- </overlayable>
+ <item type="string" name="foo" />
+ </overlayable>)";
+ EXPECT_FALSE(TestParse(input));
+ input = R"(
<overlayable>
<policy type="product">
<item type="string" name="foo" />
</policy>
- </overlayable>)";
- EXPECT_FALSE(TestParse(input));
-}
-
-TEST_F(ResourceParserTest, PolicyAndNonPolicyOverlayableError) {
- std::string input = R"(
- <overlayable policy="product">
- <item type="string" name="foo" />
- </overlayable>
- <overlayable policy="">
+ <policy type="vendor">
<item type="string" name="foo" />
- </overlayable>)";
+ </policy>
+ </overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
- <overlayable policy="">
- <item type="string" name="foo" />
- </overlayable>
- <overlayable policy="product">
- <item type="string" name="foo" />
- </overlayable>)";
- EXPECT_FALSE(TestParse(input));
-}
-
-TEST_F(ResourceParserTest, DuplicateOverlayableMultiplePolicyError) {
- std::string input = R"(
<overlayable>
- <policy type="vendor|product">
+ <policy type="product">
<item type="string" name="foo" />
</policy>
</overlayable>
+
<overlayable>
- <policy type="product_services|vendor">
+ <policy type="product">
<item type="string" name="foo" />
</policy>
</overlayable>)";
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index bc8a4d1f85b8..54633ad5c5e3 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -625,18 +625,18 @@ bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew&
return true;
}
-bool ResourceTable::AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
IDiagnostics* diag) {
- return AddOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
+ return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
}
-bool ResourceTable::AddOverlayableMangled(const ResourceNameRef& name,
+bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
const Overlayable& overlayable, IDiagnostics* diag) {
- return AddOverlayableImpl(name, overlayable, SkipNameValidator, diag);
+ return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
}
-bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
- NameValidator name_validator, IDiagnostics* diag) {
+bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+ NameValidator name_validator, IDiagnostics *diag) {
CHECK(diag != nullptr);
if (!ValidateName(name_validator, name, overlayable.source, diag)) {
@@ -647,27 +647,14 @@ bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overla
ResourceTableType* type = package->FindOrCreateType(name.type);
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
- for (auto& overlayable_declaration : entry->overlayable_declarations) {
- // An overlayable resource cannot be declared twice with the same policy
- if (overlayable.policy == overlayable_declaration.policy) {
- diag->Error(DiagMessage(overlayable.source)
+ if (entry->overlayable) {
+ diag->Error(DiagMessage(overlayable.source)
<< "duplicate overlayable declaration for resource '" << name << "'");
- diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
- return false;
- }
-
- // An overlayable resource cannot be declared once with a policy and without a policy because
- // the policy becomes unused
- if (!overlayable.policy || !overlayable_declaration.policy) {
- diag->Error(DiagMessage(overlayable.source)
- << "overlayable resource '" << name << "'"
- << " declared once with a policy and once with no policy");
- diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
- return false;
- }
+ diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
+ return false;
}
- entry->overlayable_declarations.push_back(overlayable);
+ entry->overlayable = overlayable;
return true;
}
@@ -703,7 +690,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
new_entry->id = entry->id;
new_entry->visibility = entry->visibility;
new_entry->allow_new = entry->allow_new;
- new_entry->overlayable_declarations = entry->overlayable_declarations;
+ new_entry->overlayable = entry->overlayable;
for (const auto& config_value : entry->values) {
ResourceConfigValue* new_value =
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 3dd0a769d944..e646f5be43c7 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -57,27 +57,32 @@ struct AllowNew {
std::string comment;
};
-// Represents a declaration that a resource is overayable at runtime.
+// Represents a declaration that a resource is overlayable at runtime.
struct Overlayable {
+
// Represents the types overlays that are allowed to overlay the resource.
- enum class Policy {
+ enum Policy : uint32_t {
+ kNone = 0x00,
+
// The resource can be overlaid by any overlay.
- kPublic,
+ kPublic = 0x01,
// The resource can be overlaid by any overlay on the system partition.
- kSystem,
+ kSystem = 0x02,
// The resource can be overlaid by any overlay on the vendor partition.
- kVendor,
+ kVendor = 0x04,
// The resource can be overlaid by any overlay on the product partition.
- kProduct,
+ kProduct = 0x08,
// The resource can be overlaid by any overlay on the product services partition.
- kProductServices,
+ kProductServices = 0x10
};
- Maybe<Policy> policy;
+ typedef uint32_t PolicyFlags;
+ PolicyFlags policies = Policy::kNone;
+
Source source;
std::string comment;
};
@@ -116,7 +121,7 @@ class ResourceEntry {
Maybe<AllowNew> allow_new;
// The declarations of this resource as overlayable for RROs
- std::vector<Overlayable> overlayable_declarations;
+ Maybe<Overlayable> overlayable;
// The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
@@ -246,9 +251,9 @@ class ResourceTable {
bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
const ResourceId& res_id, IDiagnostics* diag);
- bool AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
- IDiagnostics* diag);
- bool AddOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
+ bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+ IDiagnostics *diag);
+ bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
IDiagnostics* diag);
bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
@@ -323,8 +328,8 @@ class ResourceTable {
bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
NameValidator name_validator, IDiagnostics* diag);
- bool AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
- NameValidator name_validator, IDiagnostics* diag);
+ bool SetOverlayableImpl(const ResourceNameRef &name, const Overlayable &overlayable,
+ NameValidator name_validator, IDiagnostics *diag);
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
const Visibility& symbol, NameValidator name_validator,
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 7c28f07d0f66..31095c4d88c8 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -242,69 +242,50 @@ TEST(ResourceTableTest, SetAllowNew) {
ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
}
-TEST(ResourceTableTest, AddOverlayable) {
+TEST(ResourceTableTest, SetOverlayable) {
ResourceTable table;
- const ResourceName name = test::ParseNameOrDie("android:string/foo");
-
- Overlayable overlayable;
- overlayable.policy = Overlayable::Policy::kProduct;
- overlayable.comment = "first";
- ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
- Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
- ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
- ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProduct));
-
- Overlayable overlayable2;
- overlayable2.comment = "second";
- overlayable2.policy = Overlayable::Policy::kProductServices;
- ASSERT_TRUE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
- result = table.FindResource(name);
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
- ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
- ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProduct));
- ASSERT_THAT(result.value().entry->overlayable_declarations[1].comment, StrEq("second"));
- ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
- Eq(Overlayable::Policy::kProductServices));
-}
+ Overlayable overlayable{};
+ overlayable.policies |= Overlayable::Policy::kProduct;
+ overlayable.policies |= Overlayable::Policy::kProductServices;
+ overlayable.comment = "comment";
-TEST(ResourceTableTest, AddDuplicateOverlayableFail) {
- ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
+ ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+ Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name);
- Overlayable overlayable;
- overlayable.policy = Overlayable::Policy::kProduct;
- ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
- Overlayable overlayable2;
- overlayable2.policy = Overlayable::Policy::kProduct;
- ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
+ Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+ ASSERT_THAT(result_overlayable.comment, StrEq("comment"));
+ EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct
+ | Overlayable::Policy::kProductServices));
}
-TEST(ResourceTableTest, AddOverlayablePolicyAndNoneFirstFail) {
+TEST(ResourceTableTest, AddDuplicateOverlayableSamePolicyFail) {
ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
- ASSERT_TRUE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
+ Overlayable overlayable{};
+ overlayable.policies = Overlayable::Policy::kProduct;
+ ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
- Overlayable overlayable2;
- overlayable2.policy = Overlayable::Policy::kProduct;
- ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
+ Overlayable overlayable2{};
+ overlayable2.policies = Overlayable::Policy::kProduct;
+ ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics()));
}
-TEST(ResourceTableTest, AddOverlayablePolicyAndNoneLastFail) {
+TEST(ResourceTableTest, AddDuplicateOverlayableDifferentPolicyFail) {
ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
- Overlayable overlayable;
- overlayable.policy = Overlayable::Policy::kProduct;
- ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
+ Overlayable overlayable{};
+ overlayable.policies = Overlayable::Policy::kProduct;
+ ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
- ASSERT_FALSE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
+ Overlayable overlayable2{};
+ overlayable2.policies = Overlayable::Policy::kVendor;
+ ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics()));
}
TEST(ResourceTableTest, AllowDuplictaeResourcesNames) {
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index bf9fe49da2d6..81a2c2e5cc02 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -136,12 +136,11 @@ message AllowNew {
// Represents a declaration that a resource is overayable at runtime.
message Overlayable {
enum Policy {
- NONE = 0;
- PUBLIC = 1;
- SYSTEM = 2;
- VENDOR = 3;
- PRODUCT = 4;
- PRODUCT_SERVICES = 5;
+ PUBLIC = 0;
+ SYSTEM = 1;
+ VENDOR = 2;
+ PRODUCT = 3;
+ PRODUCT_SERVICES = 4;
}
// Where this declaration was defined in source.
@@ -150,8 +149,8 @@ message Overlayable {
// Any comment associated with the declaration.
string comment = 2;
- // The policy of the overlayable declaration
- Policy policy = 3;
+ // The policy defined in the overlayable declaration.
+ repeated Policy policy = 3;
}
// An entry ID in the range [0x0000, 0xffff].
@@ -181,7 +180,7 @@ message Entry {
AllowNew allow_new = 4;
// Whether this resource can be overlaid by a runtime resource overlay (RRO).
- repeated Overlayable overlayable = 5;
+ Overlayable overlayable = 5;
// The set of values defined for this entry, each corresponding to a different
// configuration/variant.
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 89d19cf4ba08..5cf056e60640 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -255,6 +255,7 @@ class DumpCommand : public Command {
AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
+ // TODO(b/120609160): Add aapt2 overlayable dump command
}
int Action(const std::vector<std::string>& args) override {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index df0daebe8453..61ebd4ee26ca 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -441,25 +441,25 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
const ResTable_overlayable_policy_header* policy_header =
ConvertTo<ResTable_overlayable_policy_header>(parser.chunk());
- std::vector<Overlayable::Policy> policies;
+ Overlayable::PolicyFlags policies = Overlayable::Policy::kNone;
if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) {
- policies.push_back(Overlayable::Policy::kPublic);
+ policies |= Overlayable::Policy::kPublic;
}
if (policy_header->policy_flags
& ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) {
- policies.push_back(Overlayable::Policy::kSystem);
+ policies |= Overlayable::Policy::kSystem;
}
if (policy_header->policy_flags
& ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) {
- policies.push_back(Overlayable::Policy::kVendor);
+ policies |= Overlayable::Policy::kVendor;
}
if (policy_header->policy_flags
& ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) {
- policies.push_back(Overlayable::Policy::kProduct);
+ policies |= Overlayable::Policy::kProduct;
}
if (policy_header->policy_flags
& ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) {
- policies.push_back(Overlayable::Policy::kProductServices);
+ policies |= Overlayable::Policy::kProductServices;
}
const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>(
@@ -478,13 +478,11 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
return false;
}
- for (Overlayable::Policy policy : policies) {
- Overlayable overlayable;
- overlayable.source = source_.WithLine(0);
- overlayable.policy = policy;
- if (!table_->AddOverlayable(iter->second, overlayable, diag_)) {
- return false;
- }
+ Overlayable overlayable{};
+ overlayable.source = source_.WithLine(0);
+ overlayable.policies = policies;
+ if (!table_->SetOverlayable(iter->second, overlayable, diag_)) {
+ return false;
}
}
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 976c3288bfca..200e2d468500 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -429,56 +429,52 @@ class PackageFlattener {
CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>";
for (auto& entry : type->entries) {
CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>";
+ if (!entry->overlayable) {
+ continue;
+ }
- // TODO(b/120298168): Convert the policies vector to a policy set or bitmask
- if (!entry->overlayable_declarations.empty()) {
- uint16_t policy_flags = 0;
- for (Overlayable overlayable : entry->overlayable_declarations) {
- if (overlayable.policy) {
- switch (overlayable.policy.value()) {
- case Overlayable::Policy::kPublic:
- policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
- break;
- case Overlayable::Policy::kSystem:
- policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
- break;
- case Overlayable::Policy::kVendor:
- policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
- break;
- case Overlayable::Policy::kProduct:
- policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
- break;
- case Overlayable::Policy::kProductServices:
- policy_flags |=
- ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION;
- break;
- }
- } else {
- // Encode overlayable entries defined without a policy as publicly overlayable
- policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
- }
- }
+ Overlayable overlayable = entry->overlayable.value();
+ uint32_t policy_flags = Overlayable::Policy::kNone;
+ if (overlayable.policies & Overlayable::Policy::kPublic) {
+ policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
+ }
+ if (overlayable.policies & Overlayable::Policy::kSystem) {
+ policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
+ }
+ if (overlayable.policies & Overlayable::Policy::kVendor) {
+ policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
+ }
+ if (overlayable.policies & Overlayable::Policy::kProduct) {
+ policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
+ }
+ if (overlayable.policies & Overlayable::Policy::kProductServices) {
+ policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION;
+ }
- // Find the overlayable policy chunk with the same policies as the entry
- PolicyChunk* policy_chunk = nullptr;
- for (PolicyChunk& policy : policies) {
- if (policy.policy_flags == policy_flags) {
- policy_chunk = &policy;
- break;
- }
- }
+ if (overlayable.policies == Overlayable::Policy::kNone) {
+ // Encode overlayable entries defined without a policy as publicly overlayable
+ policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
+ }
- // Create a new policy chunk if an existing one with the same policy cannot be found
- if (policy_chunk == nullptr) {
- PolicyChunk p;
- p.policy_flags = policy_flags;
- policies.push_back(p);
- policy_chunk = &policies.back();
+ // Find the overlayable policy chunk with the same policies as the entry
+ PolicyChunk* policy_chunk = nullptr;
+ for (PolicyChunk& policy : policies) {
+ if (policy.policy_flags == policy_flags) {
+ policy_chunk = &policy;
+ break;
}
+ }
- policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(),
- entry->id.value()));
+ // Create a new policy chunk if an existing one with the same policy cannot be found
+ if (policy_chunk == nullptr) {
+ PolicyChunk p;
+ p.policy_flags = policy_flags;
+ policies.push_back(p);
+ policy_chunk = &policies.back();
}
+
+ policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(),
+ entry->id.value()));
}
}
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 410efbe83b1b..e99ab1f37761 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -628,14 +628,17 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
}
TEST_F(TableFlattenerTest, FlattenOverlayable) {
+ Overlayable overlayable{};
+ overlayable.policies |= Overlayable::Policy::kProduct;
+ overlayable.policies |= Overlayable::Policy::kSystem;
+ overlayable.policies |= Overlayable::Policy::kVendor;
+
std::string name = "com.app.test:integer/overlayable";
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
.AddSimple(name, ResourceId(0x7f020000))
- .AddOverlayable(name, Overlayable::Policy::kProduct)
- .AddOverlayable(name, Overlayable::Policy::kSystem)
- .AddOverlayable(name, Overlayable::Policy::kVendor)
+ .SetOverlayable(name, overlayable)
.Build();
ResourceTable output_table;
@@ -644,39 +647,45 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) {
auto search_result = output_table.FindResource(test::ParseNameOrDie(name));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
- Overlayable::Policy::kSystem);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
- Overlayable::Policy::kVendor);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
- Overlayable::Policy::kProduct);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem
+ | Overlayable::Policy::kVendor
+ | Overlayable::Policy::kProduct);
}
TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
std::string name_zero = "com.app.test:integer/overlayable_zero";
+ Overlayable overlayable_zero{};
+ overlayable_zero.policies |= Overlayable::Policy::kProduct;
+ overlayable_zero.policies |= Overlayable::Policy::kSystem;
+ overlayable_zero.policies |= Overlayable::Policy::kProductServices;
+
std::string name_one = "com.app.test:integer/overlayable_one";
+ Overlayable overlayable_one{};
+ overlayable_one.policies |= Overlayable::Policy::kPublic;
+ overlayable_one.policies |= Overlayable::Policy::kProductServices;
+
std::string name_two = "com.app.test:integer/overlayable_two";
+ Overlayable overlayable_two{};
+ overlayable_two.policies |= Overlayable::Policy::kProduct;
+ overlayable_two.policies |= Overlayable::Policy::kSystem;
+ overlayable_two.policies |= Overlayable::Policy::kVendor;
+
std::string name_three = "com.app.test:integer/overlayable_three";
+ Overlayable overlayable_three{};
+
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
.AddSimple(name_zero, ResourceId(0x7f020000))
- .AddOverlayable(name_zero, Overlayable::Policy::kProduct)
- .AddOverlayable(name_zero, Overlayable::Policy::kSystem)
- .AddOverlayable(name_zero, Overlayable::Policy::kProductServices)
+ .SetOverlayable(name_zero, overlayable_zero)
.AddSimple(name_one, ResourceId(0x7f020001))
- .AddOverlayable(name_one, Overlayable::Policy::kPublic)
- .AddOverlayable(name_one, Overlayable::Policy::kSystem)
+ .SetOverlayable(name_one, overlayable_one)
.AddSimple(name_two, ResourceId(0x7f020002))
- .AddOverlayable(name_two, Overlayable::Policy::kProduct)
- .AddOverlayable(name_two, Overlayable::Policy::kSystem)
- .AddOverlayable(name_two, Overlayable::Policy::kProductServices)
+ .SetOverlayable(name_two, overlayable_two)
.AddSimple(name_three, ResourceId(0x7f020003))
- .AddOverlayable(name_three, {})
+ .SetOverlayable(name_three, overlayable_three)
.Build();
ResourceTable output_table;
@@ -685,51 +694,35 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
- Overlayable::Policy::kSystem);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
- Overlayable::Policy::kProduct);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
- Overlayable::Policy::kProductServices);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem
+ | Overlayable::Policy::kProduct
+ | Overlayable::Policy::kProductServices);
search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 2);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
- Overlayable::Policy::kPublic);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
- Overlayable::Policy::kSystem);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic
+ | Overlayable::Policy::kProductServices);
search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
- Overlayable::Policy::kSystem);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
- Overlayable::Policy::kProduct);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
- Overlayable::Policy::kProductServices);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem
+ | Overlayable::Policy::kProduct
+ | Overlayable::Policy::kVendor);
search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 1);
- EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
- EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
- Overlayable::Policy::kPublic);
-
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic);
}
-
} // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index f612914269de..cf2ab0f45ad6 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -437,37 +437,39 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
entry->allow_new = std::move(allow_new);
}
- for (const pb::Overlayable& pb_overlayable : pb_entry.overlayable()) {
- Overlayable overlayable;
- switch (pb_overlayable.policy()) {
- case pb::Overlayable::NONE:
- overlayable.policy = {};
- break;
- case pb::Overlayable::PUBLIC:
- overlayable.policy = Overlayable::Policy::kPublic;
- break;
- case pb::Overlayable::PRODUCT:
- overlayable.policy = Overlayable::Policy::kProduct;
- break;
- case pb::Overlayable::PRODUCT_SERVICES:
- overlayable.policy = Overlayable::Policy::kProductServices;
- break;
- case pb::Overlayable::SYSTEM:
- overlayable.policy = Overlayable::Policy::kSystem;
- break;
- case pb::Overlayable::VENDOR:
- overlayable.policy = Overlayable::Policy::kVendor;
- break;
- default:
- *out_error = "unknown overlayable policy";
- return false;
+ if (pb_entry.has_overlayable()) {
+ Overlayable overlayable{};
+
+ const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
+ for (const int policy : pb_overlayable.policy()) {
+ switch (policy) {
+ case pb::Overlayable::PUBLIC:
+ overlayable.policies |= Overlayable::Policy::kPublic;
+ break;
+ case pb::Overlayable::SYSTEM:
+ overlayable.policies |= Overlayable::Policy::kSystem;
+ break;
+ case pb::Overlayable::VENDOR:
+ overlayable.policies |= Overlayable::Policy::kVendor;
+ break;
+ case pb::Overlayable::PRODUCT:
+ overlayable.policies |= Overlayable::Policy::kProduct;
+ break;
+ case pb::Overlayable::PRODUCT_SERVICES:
+ overlayable.policies |= Overlayable::Policy::kProductServices;
+ break;
+ default:
+ *out_error = "unknown overlayable policy";
+ return false;
+ }
}
if (pb_overlayable.has_source()) {
DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
}
+
overlayable.comment = pb_overlayable.comment();
- entry->overlayable_declarations.push_back(overlayable);
+ entry->overlayable = overlayable;
}
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index ecf34d13e1b3..70bf8684f8a8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -310,26 +310,24 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
pb_allow_new->set_comment(entry->allow_new.value().comment);
}
- for (const Overlayable& overlayable : entry->overlayable_declarations) {
- pb::Overlayable* pb_overlayable = pb_entry->add_overlayable();
- if (overlayable.policy) {
- switch (overlayable.policy.value()) {
- case Overlayable::Policy::kPublic:
- pb_overlayable->set_policy(pb::Overlayable::PUBLIC);
- break;
- case Overlayable::Policy::kProduct:
- pb_overlayable->set_policy(pb::Overlayable::PRODUCT);
- break;
- case Overlayable::Policy::kProductServices:
- pb_overlayable->set_policy(pb::Overlayable::PRODUCT_SERVICES);
- break;
- case Overlayable::Policy::kSystem:
- pb_overlayable->set_policy(pb::Overlayable::SYSTEM);
- break;
- case Overlayable::Policy::kVendor:
- pb_overlayable->set_policy(pb::Overlayable::VENDOR);
- break;
- }
+ if (entry->overlayable) {
+ pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
+
+ Overlayable overlayable = entry->overlayable.value();
+ if (overlayable.policies & Overlayable::Policy::kPublic) {
+ pb_overlayable->add_policy(pb::Overlayable::PUBLIC);
+ }
+ if (overlayable.policies & Overlayable::Policy::kProduct) {
+ pb_overlayable->add_policy(pb::Overlayable::PRODUCT);
+ }
+ if (overlayable.policies & Overlayable::Policy::kProductServices) {
+ pb_overlayable->add_policy(pb::Overlayable::PRODUCT_SERVICES);
+ }
+ if (overlayable.policies & Overlayable::Policy::kSystem) {
+ pb_overlayable->add_policy(pb::Overlayable::SYSTEM);
+ }
+ if (overlayable.policies & Overlayable::Policy::kVendor) {
+ pb_overlayable->add_policy(pb::Overlayable::VENDOR);
}
SerializeSourceToPb(overlayable.source, &source_pool,
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 1cd2f0b9a961..fb913f409f52 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -93,7 +93,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
// Make an overlayable resource.
- ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
+ ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
Overlayable{}, test::GetDiagnostics()));
pb::ResourceTable pb_table;
@@ -160,8 +160,9 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
- EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ EXPECT_THAT(search_result.value().entry->overlayable.value().policies,
+ Eq(Overlayable::Policy::kNone));
}
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
@@ -502,15 +503,26 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) {
}
TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
+ Overlayable overlayable_foo{};
+ overlayable_foo.policies |= Overlayable::Policy::kSystem;
+ overlayable_foo.policies |= Overlayable::Policy::kProduct;
+
+ Overlayable overlayable_bar{};
+ overlayable_bar.policies |= Overlayable::Policy::kProductServices;
+ overlayable_bar.policies |= Overlayable::Policy::kVendor;
+
+ Overlayable overlayable_baz{};
+ overlayable_baz.policies |= Overlayable::Policy::kPublic;
+
+ Overlayable overlayable_biz{};
+
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kSystem)
- .AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kProduct)
- .AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kProductServices)
- .AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kVendor)
- .AddOverlayable("com.app.a:bool/baz", Overlayable::Policy::kPublic)
- .AddOverlayable("com.app.a:bool/biz", {})
+ .SetOverlayable("com.app.a:bool/foo", overlayable_foo)
+ .SetOverlayable("com.app.a:bool/bar", overlayable_bar)
+ .SetOverlayable("com.app.a:bool/baz", overlayable_baz)
+ .SetOverlayable("com.app.a:bool/biz", overlayable_biz)
.AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true"))
.Build();
@@ -523,37 +535,36 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
EXPECT_THAT(error, IsEmpty());
- Maybe<ResourceTable::SearchResult> result =
+ Maybe<ResourceTable::SearchResult> search_result =
new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
- EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kSystem));
- EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
- Eq(Overlayable::Policy::kProduct));
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kSystem
+ | Overlayable::Policy::kProduct));
- result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
- EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProductServices));
- EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
- Eq(Overlayable::Policy::kVendor));
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProductServices
+ | Overlayable::Policy::kVendor));
- result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kPublic));
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kPublic);
- result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
- EXPECT_FALSE(result.value().entry->overlayable_declarations[0].policy);
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kNone);
- result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
- ASSERT_TRUE(result);
- EXPECT_THAT(result.value().entry->overlayable_declarations.size(), Eq(0));
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
+ ASSERT_TRUE(search_result);
+ ASSERT_FALSE(search_result.value().entry->overlayable);
}
} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 1b6626a8dfe9..8cbc03738677 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -374,8 +374,8 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
}
// Ensure that definitions for values declared as overlayable exist
- if (!entry->overlayable_declarations.empty() && entry->values.empty()) {
- context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_declarations[0].source)
+ if (entry->overlayable && entry->values.empty()) {
+ context->GetDiagnostics()->Error(DiagMessage(entry->overlayable.value().source)
<< "no definition for overlayable symbol '"
<< name << "'");
error = true;
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index d777e22fa4b7..22e1723591a8 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -134,35 +134,21 @@ static bool MergeEntry(IAaptContext* context, const Source& src,
dst_entry->allow_new = std::move(src_entry->allow_new);
}
- for (auto& src_overlayable : src_entry->overlayable_declarations) {
- for (auto& dst_overlayable : dst_entry->overlayable_declarations) {
- // An overlayable resource cannot be declared twice with the same policy
- if (src_overlayable.policy == dst_overlayable.policy) {
- context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
- << "duplicate overlayable declaration for resource '"
- << src_entry->name << "'");
- context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
- << "previous declaration here");
- return false;
- }
-
- // An overlayable resource cannot be declared once with a policy and without a policy because
- // the policy becomes unused
- if (!src_overlayable.policy || !dst_overlayable.policy) {
- context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
- << "overlayable resource '" << src_entry->name
- << "' declared once with a policy and once with no "
- << "policy");
- context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
- << "previous declaration here");
- return false;
- }
+ if (src_entry->overlayable) {
+ if (dst_entry->overlayable) {
+ // Do not allow a resource with an overlayable declaration to have that overlayable
+ // declaration redefined
+ context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
+ << "duplicate overlayable declaration for resource '"
+ << src_entry->name << "'");
+ context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
+ << "previous declaration here");
+ return false;
+ } else {
+ dst_entry->overlayable = std::move(src_entry->overlayable);
}
}
- dst_entry->overlayable_declarations.insert(dst_entry->overlayable_declarations.end(),
- src_entry->overlayable_declarations.begin(),
- src_entry->overlayable_declarations.end());
return true;
}
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index d6579d37b452..17b2a83bad04 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -436,17 +436,21 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
}
-TEST_F(TableMergerTest, AddOverlayable) {
+TEST_F(TableMergerTest, SetOverlayable) {
+ Overlayable overlayable{};
+ overlayable.policies |= Overlayable::Policy::kProduct;
+ overlayable.policies |= Overlayable::Policy::kVendor;
+
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+ .SetOverlayable("bool/foo", overlayable)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", Overlayable::Policy::kProductServices)
+ .AddSimple("bool/foo")
.Build();
ResourceTable final_table;
@@ -457,26 +461,28 @@ TEST_F(TableMergerTest, AddOverlayable) {
ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo");
- Maybe<ResourceTable::SearchResult> result = final_table.FindResource(name);
- ASSERT_TRUE(result);
- ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
- ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
- Eq(Overlayable::Policy::kProduct));
- ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
- Eq(Overlayable::Policy::kProductServices));
+ Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name);
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct
+ | Overlayable::Policy::kVendor));
}
-TEST_F(TableMergerTest, AddDuplicateOverlayableFail) {
+TEST_F(TableMergerTest, SetOverlayableLater) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+ .AddSimple("bool/foo")
.Build();
+ Overlayable overlayable{};
+ overlayable.policies |= Overlayable::Policy::kPublic;
+ overlayable.policies |= Overlayable::Policy::kProductServices;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+ .SetOverlayable("bool/foo", overlayable)
.Build();
ResourceTable final_table;
@@ -484,20 +490,32 @@ TEST_F(TableMergerTest, AddDuplicateOverlayableFail) {
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
- ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
+
+ const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo");
+ Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name);
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable);
+ Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+ EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kPublic
+ | Overlayable::Policy::kProductServices));
}
-TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) {
+TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) {
+ Overlayable overlayable_first{};
+ overlayable_first.policies |= Overlayable::Policy::kProduct;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", {})
+ .SetOverlayable("bool/foo", overlayable_first)
.Build();
+ Overlayable overlayable_second{};
+ overlayable_second.policies |= Overlayable::Policy::kProduct;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+ .SetOverlayable("bool/foo", overlayable_second)
.Build();
ResourceTable final_table;
@@ -508,17 +526,21 @@ TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) {
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
-TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneLastFail) {
+TEST_F(TableMergerTest, SetOverlayableDifferentPolicesFail) {
+ Overlayable overlayable_first{};
+ overlayable_first.policies |= Overlayable::Policy::kVendor;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+ .SetOverlayable("bool/foo",overlayable_first)
.Build();
+ Overlayable overlayable_second{};
+ overlayable_second.policies |= Overlayable::Policy::kProduct;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddOverlayable("bool/foo", {})
+ .SetOverlayable("bool/foo", overlayable_second)
.Build();
ResourceTable final_table;
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 2e717ff2bc3b..9c5b5d36b798 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -248,7 +248,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
if (!split_entry->id) {
split_entry->id = entry->id;
split_entry->visibility = entry->visibility;
- split_entry->overlayable_declarations = entry->overlayable_declarations;
+ split_entry->overlayable = entry->overlayable;
}
// Copy the selected values into the new Split Entry.
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 03b59e033402..884ec38290f8 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -135,12 +135,11 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na
return *this;
}
-ResourceTableBuilder& ResourceTableBuilder::AddOverlayable(const StringPiece& name,
- const Maybe<Overlayable::Policy> p) {
+ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name,
+ const Overlayable& overlayable) {
+
ResourceName res_name = ParseNameOrDie(name);
- Overlayable overlayable;
- overlayable.policy = p;
- CHECK(table_->AddOverlayable(res_name, overlayable, GetDiagnostics()));
+ CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics()));
return *this;
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index d68c24ddc665..a12048436e38 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -73,8 +73,8 @@ class ResourceTableBuilder {
const ResourceId& id, std::unique_ptr<Value> value);
ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
Visibility::Level level, bool allow_new = false);
- ResourceTableBuilder& AddOverlayable(const android::StringPiece& name,
- Maybe<Overlayable::Policy> policy);
+ ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
+ const Overlayable& overlayable);
StringPool* string_pool();
std::unique_ptr<ResourceTable> Build();
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index c6acd026bd39..21d6b94fba24 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -193,5 +193,7 @@ interface IWifiManager
int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
+
+ String[] getFactoryMacAddresses();
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 46ecc497da31..57c97eaf1f10 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -4434,4 +4434,19 @@ public class WifiManager {
public boolean isOweSupported() {
return isFeatureSupported(WIFI_FEATURE_OWE);
}
+
+ /**
+ * Gets the factory Wi-Fi MAC addresses.
+ * @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array
+ * if failed.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public String[] getFactoryMacAddresses() {
+ try {
+ return mService.getFactoryMacAddresses();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index 0f4e3a8ba20f..36f66aa81661 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -452,4 +452,9 @@ public abstract class AbstractWifiService extends IWifiManager.Stub {
List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public String[] getFactoryMacAddresses() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 13c8c9ea7ead..1001b100cb3b 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -29,6 +29,7 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -88,6 +89,7 @@ public class WifiManagerTest {
private static final int TEST_UID = 14553;
private static final String TEST_PACKAGE_NAME = "TestPackage";
private static final String TEST_COUNTRY_CODE = "US";
+ private static final String[] TEST_MAC_ADDRESSES = {"da:a1:19:0:0:0"};
@Mock Context mContext;
@Mock
@@ -1320,4 +1322,15 @@ i * Verify that a call to cancel WPS immediately returns a failure.
assertEquals(WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP,
mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp());
}
+
+ /**
+ * Verify getting the factory MAC address.
+ * @throws Exception
+ */
+ @Test
+ public void testGetFactoryMacAddress() throws Exception {
+ when(mWifiService.getFactoryMacAddresses()).thenReturn(TEST_MAC_ADDRESSES);
+ assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses());
+ verify(mWifiService).getFactoryMacAddresses();
+ }
}