summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--StubLibraries.bp1
-rw-r--r--apex/media/framework/Android.bp26
-rw-r--r--apex/media/framework/java/android/media/MediaParser.java160
-rw-r--r--apex/media/framework/jni/android_media_MediaParserJNI.cpp92
-rw-r--r--api/current.txt24
-rw-r--r--api/system-current.txt12
-rw-r--r--api/test-current.txt3
-rw-r--r--cmds/statsd/src/atoms.proto73
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.cpp48
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.h22
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp58
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/config_update_utils.h2
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp197
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h21
-rw-r--r--cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp293
-rw-r--r--core/java/android/app/AppOpsManager.java22
-rw-r--r--core/java/android/app/PendingIntent.java2
-rw-r--r--core/java/android/app/TaskInfo.java26
-rw-r--r--core/java/android/app/WindowConfiguration.java3
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java16
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java12
-rw-r--r--core/java/android/hardware/biometrics/BiometricTestSession.java14
-rw-r--r--core/java/android/hardware/biometrics/ITestSession.aidl2
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java19
-rw-r--r--core/java/android/hardware/camera2/TotalCaptureResult.java10
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java14
-rw-r--r--core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java15
-rw-r--r--core/java/android/os/Looper.java2
-rw-r--r--core/java/android/provider/CallLog.java169
-rw-r--r--core/java/android/provider/Settings.java27
-rw-r--r--core/java/android/service/controls/ControlsProviderService.java14
-rw-r--r--core/java/android/service/controls/templates/ControlTemplate.java16
-rw-r--r--core/java/android/service/controls/templates/ThumbnailTemplate.java134
-rw-r--r--core/java/android/util/EventLog.java2
-rw-r--r--core/java/android/view/NotificationHeaderView.java82
-rw-r--r--core/java/android/view/NotificationTopLineView.java344
-rw-r--r--core/java/android/view/View.java36
-rw-r--r--core/java/android/window/ITaskOrganizerController.aidl2
-rw-r--r--core/java/android/window/TaskOrganizer.java13
-rw-r--r--core/proto/android/app/enums.proto1
-rw-r--r--core/proto/android/providers/settings/secure.proto6
-rw-r--r--core/proto/android/server/fingerprint.proto33
-rw-r--r--core/res/AndroidManifest.xml5
-rw-r--r--core/res/res/layout/notification_template_header.xml113
-rw-r--r--core/res/res/layout/notification_template_top_line.xml139
-rw-r--r--core/res/res/values/dimens.xml8
-rw-r--r--core/res/res/values/symbols.xml5
-rw-r--r--core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java115
-rw-r--r--core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java11
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--libs/WindowManager/Shell/res/raw/wm_shell_protolog.json54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java)50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java125
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java44
-rw-r--r--location/java/android/location/ILocationManager.aidl5
-rw-r--r--location/java/android/location/LocationListener.java11
-rw-r--r--location/java/android/location/LocationManager.java67
-rw-r--r--location/lib/java/com/android/location/provider/LocationProviderBase.java14
-rw-r--r--non-updatable-api/current.txt24
-rw-r--r--non-updatable-api/system-current.txt12
-rw-r--r--packages/CarSystemUI/res/values-af/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-am/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-as/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-az/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-b+sr+Latn/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-be/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-bg/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-bs/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ca/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-cs/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-da/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-el/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-en-rAU/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-en-rCA/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-en-rGB/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-en-rIN/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-en-rXC/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-es-rUS/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-es/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-et/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-fa/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-fi/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-fr-rCA/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-fr/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-hi/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-hr/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-hu/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-hy/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-in/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-is/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-it/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-iw/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ja/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ka/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-kk/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-km/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ko/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ky/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-lo/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-lt/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-lv/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-mk/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-mn/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ms/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-my/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-nb/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-nl/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-pl/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-pt-rPT/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-pt/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ro/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ru/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-si/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-sk/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-sl/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-sq/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-sr/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-sv/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-sw/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-ta/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-th/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-tl/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-tr/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-uk/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-uz/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-vi/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-zh-rCN/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-zh-rHK/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-zh-rTW/strings.xml3
-rw-r--r--packages/CarSystemUI/res/values-zu/strings.xml3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java4
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java8
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/res/layout/people_space_widget_item.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java74
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt6
-rw-r--r--packages/Tethering/tests/Android.bp23
-rw-r--r--packages/Tethering/tests/integration/Android.bp3
-rw-r--r--packages/Tethering/tests/jarjar-rules.txt (renamed from packages/Tethering/tests/unit/jarjar-rules.txt)5
-rw-r--r--packages/Tethering/tests/unit/Android.bp2
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java4
-rw-r--r--services/core/java/com/android/server/Dumpable.java40
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java64
-rw-r--r--services/core/java/com/android/server/SystemServerInitThreadPool.java34
-rw-r--r--services/core/java/com/android/server/SystemService.java14
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java55
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java6
-rw-r--r--services/core/java/com/android/server/biometrics/TEST_MAPPING7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java99
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java186
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java35
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java627
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java111
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java106
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java185
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java43
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java91
-rw-r--r--services/core/java/com/android/server/connectivity/DataConnectionStats.java4
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java103
-rwxr-xr-xservices/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java3
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java75
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java27
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java266
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java466
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecNetwork.java846
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java360
-rw-r--r--services/core/java/com/android/server/hdmi/HotplugDetectionAction.java7
-rw-r--r--services/core/java/com/android/server/hdmi/NewDeviceAction.java6
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java33
-rw-r--r--services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java8
-rw-r--r--services/core/java/com/android/server/hdmi/RoutingControlAction.java6
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java6
-rw-r--r--services/core/java/com/android/server/location/LocationProviderManager.java195
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceManager.java13
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java5
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java6
-rw-r--r--services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java4
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java69
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerRegistration.java40
-rw-r--r--services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java7
-rw-r--r--services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java7
-rw-r--r--services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java11
-rw-r--r--services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java8
-rw-r--r--services/core/java/com/android/server/pm/IncrementalStates.java27
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java38
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java8
-rw-r--r--services/core/java/com/android/server/pm/permission/BasePermission.java18
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java85
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java7
-rw-r--r--services/core/java/com/android/server/wm/Task.java124
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java64
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java46
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java136
-rw-r--r--services/java/com/android/server/SystemServer.java100
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java57
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java57
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java114
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java79
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java449
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java22
-rw-r--r--telephony/api/system-current.txt10
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java1
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java143
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl16
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt6
-rwxr-xr-xtools/hiddenapi/generate_hiddenapi_lists.py111
258 files changed, 7217 insertions, 2873 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 9604466a0edd..754c4e94e73a 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -77,6 +77,7 @@ stubs_defaults {
"android.hardware.vibrator-V1.3-java",
"framework-protos",
],
+ high_mem: true, // Lots of sources => high memory use, see b/170701554
installable: false,
annotations_enabled: true,
previous_api: ":android.api.public.latest",
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 813631e28a88..b3c9a9aca444 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -35,7 +35,6 @@ java_library {
libs: [
"framework_media_annotation",
],
-
static_libs: [
"exoplayer2-extractor"
],
@@ -111,10 +110,33 @@ java_sdk_library {
impl_library_visibility: ["//frameworks/av/apex:__subpackages__"],
}
-
java_library {
name: "framework_media_annotation",
srcs: [":framework-media-annotation-srcs"],
installable: false,
sdk_version: "core_current",
}
+
+cc_library_shared {
+ name: "libmediaparser-jni",
+ srcs: [
+ "jni/android_media_MediaParserJNI.cpp",
+ ],
+ header_libs: ["jni_headers"],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libmediametrics",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wunreachable-code",
+ "-Wunused",
+ ],
+ apex_available: [
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
+}
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index e4b5d19e67c9..045b4136a710 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -75,6 +75,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Function;
/**
* Parses media container formats and extracts contained media samples and metadata.
@@ -882,6 +884,7 @@ public final class MediaParser {
// Private constants.
private static final String TAG = "MediaParser";
+ private static final String JNI_LIBRARY_NAME = "mediaparser-jni";
private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME;
private static final Map<String, Class> EXPECTED_TYPE_BY_PARAMETER_NAME;
private static final String TS_MODE_SINGLE_PMT = "single_pmt";
@@ -889,6 +892,14 @@ public final class MediaParser {
private static final String TS_MODE_HLS = "hls";
private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ private static final String MEDIAMETRICS_ELEMENT_SEPARATOR = "|";
+ private static final int MEDIAMETRICS_MAX_STRING_SIZE = 200;
+ private static final int MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH;
+ /**
+ * Intentional error introduced to reported metrics to prevent identification of the parsed
+ * media. Note: Increasing this value may cause older hostside CTS tests to fail.
+ */
+ private static final float MEDIAMETRICS_DITHER = .02f;
@IntDef(
value = {
@@ -920,7 +931,7 @@ public final class MediaParser {
@NonNull @ParserName String name, @NonNull OutputConsumer outputConsumer) {
String[] nameAsArray = new String[] {name};
assertValidNames(nameAsArray);
- return new MediaParser(outputConsumer, /* sniff= */ false, name);
+ return new MediaParser(outputConsumer, /* createdByName= */ true, name);
}
/**
@@ -940,7 +951,7 @@ public final class MediaParser {
if (parserNames.length == 0) {
parserNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]);
}
- return new MediaParser(outputConsumer, /* sniff= */ true, parserNames);
+ return new MediaParser(outputConsumer, /* createdByName= */ false, parserNames);
}
// Misc static methods.
@@ -1052,6 +1063,14 @@ public final class MediaParser {
private long mPendingSeekPosition;
private long mPendingSeekTimeMicros;
private boolean mLoggedSchemeInitDataCreationException;
+ private boolean mReleased;
+
+ // MediaMetrics fields.
+ private final boolean mCreatedByName;
+ private final SparseArray<Format> mTrackFormats;
+ private String mLastObservedExceptionName;
+ private long mDurationMillis;
+ private long mResourceByteCount;
// Public methods.
@@ -1166,11 +1185,16 @@ public final class MediaParser {
if (mExtractorInput == null) {
// TODO: For efficiency, the same implementation should be used, by providing a
// clearBuffers() method, or similar.
+ long resourceLength = seekableInputReader.getLength();
+ if (resourceLength == -1) {
+ mResourceByteCount = -1;
+ }
+ if (mResourceByteCount != -1) {
+ mResourceByteCount += resourceLength;
+ }
mExtractorInput =
new DefaultExtractorInput(
- mExoDataReader,
- seekableInputReader.getPosition(),
- seekableInputReader.getLength());
+ mExoDataReader, seekableInputReader.getPosition(), resourceLength);
}
mExoDataReader.mInputReader = seekableInputReader;
@@ -1195,7 +1219,10 @@ public final class MediaParser {
}
}
if (mExtractor == null) {
- throw UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
+ UnrecognizedInputFormatException exception =
+ UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
+ mLastObservedExceptionName = exception.getClass().getName();
+ throw exception;
}
return true;
}
@@ -1223,8 +1250,13 @@ public final class MediaParser {
int result;
try {
result = mExtractor.read(mExtractorInput, mPositionHolder);
- } catch (ParserException e) {
- throw new ParsingException(e);
+ } catch (Exception e) {
+ mLastObservedExceptionName = e.getClass().getName();
+ if (e instanceof ParserException) {
+ throw new ParsingException((ParserException) e);
+ } else {
+ throw e;
+ }
}
if (result == Extractor.RESULT_END_OF_INPUT) {
mExtractorInput = null;
@@ -1264,21 +1296,64 @@ public final class MediaParser {
* invoked.
*/
public void release() {
- // TODO: Dump media metrics here.
mExtractorInput = null;
mExtractor = null;
+ if (mReleased) {
+ // Nothing to do.
+ return;
+ }
+ mReleased = true;
+
+ String trackMimeTypes = buildMediaMetricsString(format -> format.sampleMimeType);
+ String trackCodecs = buildMediaMetricsString(format -> format.codecs);
+ int videoWidth = -1;
+ int videoHeight = -1;
+ for (int i = 0; i < mTrackFormats.size(); i++) {
+ Format format = mTrackFormats.valueAt(i);
+ if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) {
+ videoWidth = format.width;
+ videoHeight = format.height;
+ break;
+ }
+ }
+
+ String alteredParameters =
+ String.join(
+ MEDIAMETRICS_ELEMENT_SEPARATOR,
+ mParserParameters.keySet().toArray(new String[0]));
+ alteredParameters =
+ alteredParameters.substring(
+ 0,
+ Math.min(
+ alteredParameters.length(),
+ MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
+
+ nativeSubmitMetrics(
+ mParserName,
+ mCreatedByName,
+ String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
+ mLastObservedExceptionName,
+ addDither(mResourceByteCount),
+ addDither(mDurationMillis),
+ trackMimeTypes,
+ trackCodecs,
+ alteredParameters,
+ videoWidth,
+ videoHeight);
}
// Private methods.
- private MediaParser(OutputConsumer outputConsumer, boolean sniff, String... parserNamesPool) {
+ private MediaParser(
+ OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
throw new UnsupportedOperationException("Android version must be R or greater.");
}
mParserParameters = new HashMap<>();
mOutputConsumer = outputConsumer;
mParserNamesPool = parserNamesPool;
- mParserName = sniff ? PARSER_NAME_UNKNOWN : parserNamesPool[0];
+ mCreatedByName = createdByName;
+ mParserName = createdByName ? parserNamesPool[0] : PARSER_NAME_UNKNOWN;
mPositionHolder = new PositionHolder();
mExoDataReader = new InputReadingDataReader();
removePendingSeek();
@@ -1286,6 +1361,24 @@ public final class MediaParser {
mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter();
mSchemeInitDataConstructor = getSchemeInitDataConstructor();
mMuxedCaptionFormats = new ArrayList<>();
+
+ // MediaMetrics.
+ mTrackFormats = new SparseArray<>();
+ mLastObservedExceptionName = "";
+ mDurationMillis = -1;
+ }
+
+ private String buildMediaMetricsString(Function<Format, String> formatFieldGetter) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < mTrackFormats.size(); i++) {
+ if (i > 0) {
+ stringBuilder.append(MEDIAMETRICS_ELEMENT_SEPARATOR);
+ }
+ String fieldValue = formatFieldGetter.apply(mTrackFormats.valueAt(i));
+ stringBuilder.append(fieldValue != null ? fieldValue : "");
+ }
+ return stringBuilder.substring(
+ 0, Math.min(stringBuilder.length(), MEDIAMETRICS_MAX_STRING_SIZE));
}
private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) {
@@ -1528,6 +1621,10 @@ public final class MediaParser {
@Override
public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
+ long durationUs = exoplayerSeekMap.getDurationUs();
+ if (durationUs != C.TIME_UNSET) {
+ mDurationMillis = C.usToMs(durationUs);
+ }
if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) {
ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap;
MediaFormat mediaFormat = new MediaFormat();
@@ -1575,6 +1672,7 @@ public final class MediaParser {
@Override
public void format(Format format) {
+ mTrackFormats.put(mTrackIndex, format);
mOutputConsumer.onTrackDataFound(
mTrackIndex,
new TrackData(
@@ -2031,6 +2129,20 @@ public final class MediaParser {
return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position);
}
+ /**
+ * Introduces random error to the given metric value in order to prevent the identification of
+ * the parsed media.
+ */
+ private static long addDither(long value) {
+ // Generate a random in [0, 1].
+ double randomDither = ThreadLocalRandom.current().nextFloat();
+ // Clamp the random number to [0, 2 * MEDIAMETRICS_DITHER].
+ randomDither *= 2 * MEDIAMETRICS_DITHER;
+ // Translate the random number to [1 - MEDIAMETRICS_DITHER, 1 + MEDIAMETRICS_DITHER].
+ randomDither += 1 - MEDIAMETRICS_DITHER;
+ return value != -1 ? (long) (value * randomDither) : -1;
+ }
+
private static void assertValidNames(@NonNull String[] names) {
for (String name : names) {
if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) {
@@ -2070,9 +2182,26 @@ public final class MediaParser {
}
}
+ // Native methods.
+
+ private native void nativeSubmitMetrics(
+ String parserName,
+ boolean createdByName,
+ String parserPool,
+ String lastObservedExceptionName,
+ long resourceByteCount,
+ long durationMillis,
+ String trackMimeTypes,
+ String trackCodecs,
+ String alteredParameters,
+ int videoWidth,
+ int videoHeight);
+
// Static initialization.
static {
+ System.loadLibrary(JNI_LIBRARY_NAME);
+
// Using a LinkedHashMap to keep the insertion order when iterating over the keys.
LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>();
// Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering,
@@ -2125,6 +2254,15 @@ public final class MediaParser {
// We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters
// instead. Checking that the value is a List is insufficient to catch wrong parameter
// value types.
+ int sumOfParameterNameLengths =
+ expectedTypeByParameterName.keySet().stream()
+ .map(String::length)
+ .reduce(0, Integer::sum);
+ sumOfParameterNameLengths += PARAMETER_EXPOSE_CAPTION_FORMATS.length();
+ // Add space for any required separators.
+ MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH =
+ sumOfParameterNameLengths + expectedTypeByParameterName.size();
+
EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
}
}
diff --git a/apex/media/framework/jni/android_media_MediaParserJNI.cpp b/apex/media/framework/jni/android_media_MediaParserJNI.cpp
new file mode 100644
index 000000000000..7fc4628984f5
--- /dev/null
+++ b/apex/media/framework/jni/android_media_MediaParserJNI.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <media/MediaMetrics.h>
+
+#define JNI_FUNCTION(RETURN_TYPE, NAME, ...) \
+ extern "C" { \
+ JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
+ ##__VA_ARGS__); \
+ } \
+ JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
+ ##__VA_ARGS__)
+
+namespace {
+
+constexpr char kMediaMetricsKey[] = "mediaparser";
+
+constexpr char kAttributeParserName[] = "android.media.mediaparser.parserName";
+constexpr char kAttributeCreatedByName[] = "android.media.mediaparser.createdByName";
+constexpr char kAttributeParserPool[] = "android.media.mediaparser.parserPool";
+constexpr char kAttributeLastException[] = "android.media.mediaparser.lastException";
+constexpr char kAttributeResourceByteCount[] = "android.media.mediaparser.resourceByteCount";
+constexpr char kAttributeDurationMillis[] = "android.media.mediaparser.durationMillis";
+constexpr char kAttributeTrackMimeTypes[] = "android.media.mediaparser.trackMimeTypes";
+constexpr char kAttributeTrackCodecs[] = "android.media.mediaparser.trackCodecs";
+constexpr char kAttributeAlteredParameters[] = "android.media.mediaparser.alteredParameters";
+constexpr char kAttributeVideoWidth[] = "android.media.mediaparser.videoWidth";
+constexpr char kAttributeVideoHeight[] = "android.media.mediaparser.videoHeight";
+
+// Util class to handle string resource management.
+class JstringHandle {
+public:
+ JstringHandle(JNIEnv* env, jstring value) : mEnv(env), mJstringValue(value) {
+ mCstringValue = env->GetStringUTFChars(value, /* isCopy= */ nullptr);
+ }
+
+ ~JstringHandle() {
+ if (mCstringValue != nullptr) {
+ mEnv->ReleaseStringUTFChars(mJstringValue, mCstringValue);
+ }
+ }
+
+ [[nodiscard]] const char* value() const {
+ return mCstringValue != nullptr ? mCstringValue : "";
+ }
+
+ JNIEnv* mEnv;
+ jstring mJstringValue;
+ const char* mCstringValue;
+};
+
+} // namespace
+
+JNI_FUNCTION(void, nativeSubmitMetrics, jstring parserNameJstring, jboolean createdByName,
+ jstring parserPoolJstring, jstring lastExceptionJstring, jlong resourceByteCount,
+ jlong durationMillis, jstring trackMimeTypesJstring, jstring trackCodecsJstring,
+ jstring alteredParameters, jint videoWidth, jint videoHeight) {
+ mediametrics_handle_t item(mediametrics_create(kMediaMetricsKey));
+ mediametrics_setCString(item, kAttributeParserName,
+ JstringHandle(env, parserNameJstring).value());
+ mediametrics_setInt32(item, kAttributeCreatedByName, createdByName ? 1 : 0);
+ mediametrics_setCString(item, kAttributeParserPool,
+ JstringHandle(env, parserPoolJstring).value());
+ mediametrics_setCString(item, kAttributeLastException,
+ JstringHandle(env, lastExceptionJstring).value());
+ mediametrics_setInt64(item, kAttributeResourceByteCount, resourceByteCount);
+ mediametrics_setInt64(item, kAttributeDurationMillis, durationMillis);
+ mediametrics_setCString(item, kAttributeTrackMimeTypes,
+ JstringHandle(env, trackMimeTypesJstring).value());
+ mediametrics_setCString(item, kAttributeTrackCodecs,
+ JstringHandle(env, trackCodecsJstring).value());
+ mediametrics_setCString(item, kAttributeAlteredParameters,
+ JstringHandle(env, alteredParameters).value());
+ mediametrics_setInt32(item, kAttributeVideoWidth, videoWidth);
+ mediametrics_setInt32(item, kAttributeVideoHeight, videoHeight);
+ mediametrics_selfRecord(item);
+ mediametrics_delete(item);
+}
diff --git a/api/current.txt b/api/current.txt
index 1581960852d8..b35940449f9e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -97,6 +97,7 @@ package android {
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
+ field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
@@ -17864,6 +17865,7 @@ package android.hardware.camera2 {
public class CaptureResult extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureResult.Key<?>> {
method @Nullable public <T> T get(android.hardware.camera2.CaptureResult.Key<T>);
+ method @NonNull public String getCameraId();
method public long getFrameNumber();
method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getKeys();
method @NonNull public android.hardware.camera2.CaptureRequest getRequest();
@@ -38882,6 +38884,9 @@ package android.provider {
ctor public CallLog.Calls();
method public static String getLastOutgoingCall(android.content.Context);
field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
+ field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L
+ field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L
+ field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final String BLOCK_REASON = "block_reason";
field public static final int BLOCK_REASON_BLOCKED_NUMBER = 3; // 0x3
@@ -38927,6 +38932,8 @@ package android.provider {
field public static final String IS_READ = "is_read";
field public static final String LAST_MODIFIED = "last_modified";
field public static final String LIMIT_PARAM_KEY = "limit";
+ field public static final String MISSED_REASON = "missed_reason";
+ field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L
field public static final int MISSED_TYPE = 3; // 0x3
field public static final String NEW = "new";
field public static final String NUMBER = "number";
@@ -38943,6 +38950,13 @@ package android.provider {
field public static final int REJECTED_TYPE = 5; // 0x5
field public static final String TRANSCRIPTION = "transcription";
field public static final String TYPE = "type";
+ field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L
+ field public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 2097152L; // 0x200000L
+ field public static final long USER_MISSED_DND_MODE = 262144L; // 0x40000L
+ field public static final long USER_MISSED_LOW_RING_VOLUME = 524288L; // 0x80000L
+ field public static final long USER_MISSED_NO_ANSWER = 65536L; // 0x10000L
+ field public static final long USER_MISSED_NO_VIBRATE = 1048576L; // 0x100000L
+ field public static final long USER_MISSED_SHORT_RING = 131072L; // 0x20000L
field public static final String VIA_NUMBER = "via_number";
field public static final int VOICEMAIL_TYPE = 4; // 0x4
field public static final String VOICEMAIL_URI = "voicemail_uri";
@@ -43850,6 +43864,7 @@ package android.service.controls.templates {
field public static final int TYPE_RANGE = 2; // 0x2
field public static final int TYPE_STATELESS = 8; // 0x8
field public static final int TYPE_TEMPERATURE = 7; // 0x7
+ field public static final int TYPE_THUMBNAIL = 3; // 0x3
field public static final int TYPE_TOGGLE = 1; // 0x1
field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6
}
@@ -43889,6 +43904,14 @@ package android.service.controls.templates {
field public static final int MODE_UNKNOWN = 0; // 0x0
}
+ public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
+ ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
+ method @NonNull public CharSequence getContentDescription();
+ method public int getTemplateType();
+ method @NonNull public android.graphics.drawable.Icon getThumbnail();
+ method public boolean isActive();
+ }
+
public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate);
ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate);
@@ -47883,6 +47906,7 @@ package android.telephony {
method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
+ method public int getNrState();
method @Nullable public String getRegisteredPlmn();
method public int getTransportType();
method public boolean isRegistered();
diff --git a/api/system-current.txt b/api/system-current.txt
index de62fd80c92c..f30f756ae3f6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -403,6 +403,7 @@ package android.app {
field public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats";
field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
+ field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
field public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
@@ -917,7 +918,6 @@ package android.app.admin {
field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2
field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0
field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
- field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
@@ -11804,6 +11804,7 @@ package android.telephony {
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
+ method public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11837,6 +11838,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+ method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
@@ -11876,6 +11878,11 @@ package android.telephony {
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -11908,6 +11915,9 @@ package android.telephony {
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
+ field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index 59020068e342..edf860665fb6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -210,6 +210,7 @@ package android.app {
field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
+ field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final int OP_COARSE_LOCATION = 0; // 0x0
field public static final int OP_RECORD_AUDIO = 27; // 0x1b
field public static final int OP_START_FOREGROUND = 76; // 0x4c
@@ -2374,7 +2375,7 @@ package android.window {
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.app.ActivityManager.RunningTaskInfo createRootTask(int, int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.window.WindowContainerToken getImeTarget(int);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c9356c55e6a1..084959b5071a 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -498,6 +498,7 @@ message Atom {
CarrierIdMismatchEvent carrier_id_mismatch_event = 313 [(module) = "telephony"];
CarrierIdMatchingTable carrier_id_table_update = 314 [(module) = "telephony"];
DataStallRecoveryReported data_stall_recovery_reported = 315 [(module) = "telephony"];
+ MediametricsMediaParserReported mediametrics_mediaparser_reported = 316;
// StatsdStats tracks platform atoms with ids upto 500.
// Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -4659,7 +4660,7 @@ message PrivacyIndicatorsInteracted {
UNKNOWN = 0;
CHIP_VIEWED = 1;
CHIP_CLICKED = 2;
- reserved 3; // Used only in beta builds, never shipped
+ reserved 3; // Used only in beta builds, never shipped
DIALOG_DISMISS = 4;
DIALOG_LINE_ITEM = 5;
}
@@ -8197,6 +8198,72 @@ message MediametricsExtractorReported {
}
/**
+ * Track MediaParser (parsing video/audio streams from containers) usage
+ * Logged from:
+ *
+ * frameworks/av/services/mediametrics/statsd_mediaparser.cpp
+ * frameworks/base/apex/media/framework/jni/android_media_MediaParserJNI.cpp
+ */
+message MediametricsMediaParserReported {
+ optional int64 timestamp_nanos = 1;
+ optional string package_name = 2;
+ optional int64 package_version_code = 3;
+
+ // MediaParser specific data.
+ /**
+ * The name of the parser selected for parsing the media, or an empty string
+ * if no parser was selected.
+ */
+ optional string parser_name = 4;
+ /**
+ * Whether the parser was created by name. 1 represents true, and 0
+ * represents false.
+ */
+ optional int32 created_by_name = 5;
+ /**
+ * The parser names in the sniffing pool separated by "|".
+ */
+ optional string parser_pool = 6;
+ /**
+ * The fully qualified name of the last encountered exception, or an empty
+ * string if no exception was encountered.
+ */
+ optional string last_exception = 7;
+ /**
+ * The size of the parsed media in bytes, or -1 if unknown. Note this value
+ * contains intentional random error to prevent media content
+ * identification.
+ */
+ optional int64 resource_byte_count = 8;
+ /**
+ * The duration of the media in milliseconds, or -1 if unknown. Note this
+ * value contains intentional random error to prevent media content
+ * identification.
+ */
+ optional int64 duration_millis = 9;
+ /**
+ * The MIME types of the tracks separated by "|".
+ */
+ optional string track_mime_types = 10;
+ /**
+ * The tracks' RFC 6381 codec strings separated by "|".
+ */
+ optional string track_codecs = 11;
+ /**
+ * Concatenation of the parameters altered by the client, separated by "|".
+ */
+ optional string altered_parameters = 12;
+ /**
+ * The video width in pixels, or -1 if unknown or not applicable.
+ */
+ optional int32 video_width = 13;
+ /**
+ * The video height in pixels, or -1 if unknown or not applicable.
+ */
+ optional int32 video_height = 14;
+}
+
+/**
* Track how we arbitrate between microphone/input requests.
* Logged from
* frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -10737,6 +10804,10 @@ message CarrierIdMismatchEvent {
// SPN value of the SIM card.
optional string spn = 4;
+
+ // First record of the PNN in the SIM card. This field is populated only if the SPN is missing
+ // or empty.
+ optional string pnn = 5;
}
/**
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 3d57cfe318c5..22fdf1604435 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -18,12 +18,14 @@
#include "Log.h"
#include "ValueMetricProducer.h"
-#include "../guardrail/StatsdStats.h"
-#include "../stats_log_util.h"
#include <limits.h>
#include <stdlib.h>
+#include "../guardrail/StatsdStats.h"
+#include "../stats_log_util.h"
+#include "metrics/parsing_utils/metrics_manager_util.h"
+
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_DOUBLE;
@@ -184,6 +186,48 @@ ValueMetricProducer::~ValueMetricProducer() {
}
}
+bool ValueMetricProducer::onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!MetricProducer::onConfigUpdatedLocked(
+ config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+ return false;
+ }
+
+ const ValueMetric& metric = config.value_metric(configIndex);
+ // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps.
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+ trackerToMetricMap, mWhatMatcherIndex)) {
+ return false;
+ }
+
+ if (metric.has_condition() &&
+ !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, mConditionTrackerIndex,
+ conditionToMetricMap)) {
+ return false;
+ }
+ sp<EventMatcherWizard> tmpEventWizard = mEventMatcherWizard;
+ mEventMatcherWizard = matcherWizard;
+ return true;
+}
+
void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId,
const HashableDimensionKey& primaryKey,
const FieldValue& oldState, const FieldValue& newState) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 67de214e655c..ebd8fecd55d0 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -47,7 +47,7 @@ struct PastValueBucket {
// - a condition change
// - an app upgrade
// - an alarm set to the end of the bucket
-class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+class ValueMetricProducer : public MetricProducer, public virtual PullDataReceiver {
public:
ValueMetricProducer(
const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex,
@@ -155,7 +155,23 @@ private:
// causes the bucket to be invalidated will not notify StatsdStats.
void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
- const int mWhatMatcherIndex;
+ bool onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation) override;
+
+ int mWhatMatcherIndex;
sp<EventMatcherWizard> mEventMatcherWizard;
@@ -370,6 +386,8 @@ private:
FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue);
FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateValueMetrics);
+
friend class ValueMetricProducerTestHelper;
};
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index b6e5d8846fd9..335f7753e5e3 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -580,7 +580,6 @@ bool determineAllMetricUpdateStatuses(const StatsdConfig& config,
return false;
}
}
- // TODO: determine update status for value metrics.
return true;
}
@@ -644,7 +643,7 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
set<int64_t>& noReportMetricIds,
unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
- vector<int>& metricsWithActivation) {
+ vector<int>& metricsWithActivation, set<int64_t>& replacedMetrics) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
@@ -690,6 +689,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createCountMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, currentTimeNs, metric, metricIndex,
@@ -727,6 +728,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createDurationMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, currentTimeNs, metric, metricIndex,
@@ -749,8 +752,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
newMetricProducers.push_back(producer.value());
}
for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
- newMetricProducerMap[config.event_metric(i).id()] = metricIndex;
const EventMetric& metric = config.event_metric(i);
+ newMetricProducerMap[metric.id()] = metricIndex;
optional<sp<MetricProducer>> producer;
switch (metricsToUpdate[metricIndex]) {
case UPDATE_PRESERVE: {
@@ -764,6 +767,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createEventMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers,
@@ -784,6 +789,47 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
}
newMetricProducers.push_back(producer.value());
}
+
+ for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) {
+ const ValueMetric& metric = config.value_metric(i);
+ newMetricProducerMap[metric.id()] = metricIndex;
+ optional<sp<MetricProducer>> producer;
+ switch (metricsToUpdate[metricIndex]) {
+ case UPDATE_PRESERVE: {
+ producer = updateMetric(
+ config, i, metricIndex, metric.id(), allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
+ case UPDATE_NEW: {
+ producer = createValueMetricProducerAndUpdateMetadata(
+ key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
+ stateAtomIdMap, allStateGroupMaps, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ default: {
+ ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+ (long long)metric.id());
+ return false;
+ }
+ }
+ if (!producer) {
+ return false;
+ }
+ newMetricProducers.push_back(producer.value());
+ }
+
for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) {
const GaugeMetric& metric = config.gauge_metric(i);
newMetricProducerMap[metric.id()] = metricIndex;
@@ -800,6 +846,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
break;
}
case UPDATE_REPLACE:
+ replacedMetrics.insert(metric.id());
+ [[fallthrough]]; // Intentionally fallthrough to create the new metric producer.
case UPDATE_NEW: {
producer = createGaugeMetricProducerAndUpdateMetadata(
key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex,
@@ -821,7 +869,6 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64
}
newMetricProducers.push_back(producer.value());
}
- // TODO: perform update for value metric.
const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(),
config.whitelisted_atom_ids().end());
@@ -872,6 +919,7 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const
set<int64_t>& noReportMetricIds) {
set<int64_t> replacedMatchers;
set<int64_t> replacedConditions;
+ set<int64_t> replacedMetrics;
vector<ConditionState> conditionCache;
unordered_map<int64_t, int> stateAtomIdMap;
unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
@@ -913,7 +961,7 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const
replacedStates, oldMetricProducerMap, oldMetricProducers,
newMetricProducerMap, newMetricProducers, conditionToMetricMap,
trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap,
- deactivationTrackerToMetricMap, metricsWithActivation)) {
+ deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics)) {
ALOGE("initMetricProducers failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
index 34d7e9c7de9e..3f1c5326b569 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -187,7 +187,7 @@ bool updateMetrics(
std::set<int64_t>& noReportMetricIds,
std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
- std::vector<int>& metricsWithActivation);
+ std::vector<int>& metricsWithActivation, std::set<int64_t>& replacedMetrics);
// Updates the existing MetricsManager from a new StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index b7dc2c7fd0de..8fc039a7d6b3 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -599,6 +599,108 @@ optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
eventDeactivationMap)};
}
+optional<sp<MetricProducer>> createValueMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const ValueMetric& metric, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!metric.has_id() || !metric.has_what()) {
+ ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+ if (!metric.has_value_field()) {
+ ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+ std::vector<Matcher> fieldMatchers;
+ translateFieldMatcher(metric.value_field(), &fieldMatchers);
+ if (fieldMatchers.size() < 1) {
+ ALOGE("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+
+ int trackerIndex;
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+ metric.has_dimensions_in_what(),
+ allAtomMatchingTrackers, atomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return nullopt;
+ }
+
+ sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
+ // If it is pulled atom, it should be simple matcher with one tagId.
+ if (atomMatcher->getAtomIds().size() != 1) {
+ return nullopt;
+ }
+ int atomTagId = *(atomMatcher->getAtomIds().begin());
+ int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
+ return nullopt;
+ }
+ } else if (metric.links_size() > 0) {
+ ALOGE("metrics has a MetricConditionLink but doesn't have a condition");
+ return nullopt;
+ }
+
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return nullopt;
+ }
+ } else if (metric.state_link_size() > 0) {
+ ALOGE("ValueMetric has a MetricStateLink but doesn't have a sliced state");
+ return nullopt;
+ }
+
+ // Check that all metric state links are a subset of dimensions_in_what fields.
+ std::vector<Matcher> dimensionsInWhat;
+ translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+ for (const auto& stateLink : metric.state_link()) {
+ if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+ return nullopt;
+ }
+ }
+
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap,
+ atomMatchingTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation,
+ eventActivationMap, eventDeactivationMap)) {
+ return nullopt;
+ }
+
+ uint64_t metricHash;
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ return nullopt;
+ }
+
+ return {new ValueMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+ metricHash, trackerIndex, matcherWizard, pullTagId, timeBaseNs,
+ currentTimeNs, pullerManager, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
+}
+
optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
@@ -911,97 +1013,20 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
// build ValueMetricProducer
for (int i = 0; i < config.value_metric_size(); i++) {
- const ValueMetric& metric = config.value_metric(i);
- if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
- return false;
- }
- if (!metric.has_value_field()) {
- ALOGW("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
- return false;
- }
- std::vector<Matcher> fieldMatchers;
- translateFieldMatcher(metric.value_field(), &fieldMatchers);
- if (fieldMatchers.size() < 1) {
- ALOGW("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
- return false;
- }
-
int metricIndex = allMetricProducers.size();
+ const ValueMetric& metric = config.value_metric(i);
metricMap.insert({metric.id(), metricIndex});
- int trackerIndex;
- if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
- metric.has_dimensions_in_what(),
- allAtomMatchingTrackers, atomMatchingTrackerMap,
- trackerToMetricMap, trackerIndex)) {
- return false;
- }
-
- sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
- // If it is pulled atom, it should be simple matcher with one tagId.
- if (atomMatcher->getAtomIds().size() != 1) {
- return false;
- }
- int atomTagId = *(atomMatcher->getAtomIds().begin());
- int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
-
- int conditionIndex = -1;
- if (metric.has_condition()) {
- bool good = handleMetricWithConditions(
- metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
- allConditionTrackers, conditionIndex, conditionToMetricMap);
- if (!good) {
- return false;
- }
- } else {
- if (metric.links_size() > 0) {
- ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
- return false;
- }
- }
-
- std::vector<int> slicedStateAtoms;
- unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
- if (metric.slice_by_state_size() > 0) {
- if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
- allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
- return false;
- }
- } else {
- if (metric.state_link_size() > 0) {
- ALOGW("ValueMetric has a MetricStateLink but doesn't have a sliced state");
- return false;
- }
- }
-
- // Check that all metric state links are a subset of dimensions_in_what fields.
- std::vector<Matcher> dimensionsInWhat;
- translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
- for (const auto& stateLink : metric.state_link()) {
- if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
- return false;
- }
- }
-
- unordered_map<int, shared_ptr<Activation>> eventActivationMap;
- unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- bool success = handleMetricActivation(
- config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+ optional<sp<MetricProducer>> producer = createValueMetricProducerAndUpdateMetadata(
+ key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex,
+ allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap,
+ allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap);
- if (!success) return false;
-
- uint64_t metricHash;
- if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ metricsWithActivation);
+ if (!producer) {
return false;
}
-
- sp<MetricProducer> valueProducer = new ValueMetricProducer(
- key, metric, conditionIndex, initialConditionCache, wizard, metricHash,
- trackerIndex, matcherWizard, pullTagId, timeBaseTimeNs, currentTimeNs,
- pullerManager, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
- stateGroupMap);
- allMetricProducers.push_back(valueProducer);
+ allMetricProducers.push_back(producer.value());
}
// Gauge metrics.
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index 6d1e6dde7e89..e4585cd578f8 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -148,6 +148,27 @@ optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
std::vector<int>& metricsWithActivation);
+// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with
+// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
+optional<sp<MetricProducer>> createValueMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const ValueMetric& metric, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::unordered_map<int64_t, int>& stateAtomIdMap,
+ const std::unordered_map<int64_t, std::unordered_map<int, int64_t>>& allStateGroupMaps,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
// Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with
// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 0066030ade54..4fa9bf6ffc01 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -29,6 +29,7 @@
#include "src/matchers/CombinationAtomMatchingTracker.h"
#include "src/metrics/DurationMetricProducer.h"
#include "src/metrics/GaugeMetricProducer.h"
+#include "src/metrics/ValueMetricProducer.h"
#include "src/metrics/parsing_utils/metrics_manager_util.h"
#include "tests/statsd_test_util.h"
@@ -1811,6 +1812,7 @@ TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) {
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -1819,13 +1821,14 @@ TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) {
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{event1Id, event1Index}, {event2Id, event2Index}, {event3Id, event3Index},
{event4Id, event4Index}, {event6Id, event6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({event2Id, event3Id, event4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
@@ -2040,6 +2043,7 @@ TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) {
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2048,13 +2052,14 @@ TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) {
oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{count1Id, count1Index}, {count2Id, count2Index}, {count3Id, count3Index},
{count4Id, count4Index}, {count6Id, count6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({count2Id, count3Id, count4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
@@ -2249,6 +2254,7 @@ TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) {
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2257,13 +2263,14 @@ TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) {
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{gauge1Id, gauge1Index}, {gauge2Id, gauge2Index}, {gauge3Id, gauge3Index},
{gauge4Id, gauge4Index}, {gauge6Id, gauge6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({gauge2Id, gauge3Id, gauge4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
@@ -2566,6 +2573,7 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) {
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
@@ -2574,7 +2582,7 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) {
oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
{duration1Id, duration1Index}, {duration2Id, duration2Index},
@@ -2582,7 +2590,7 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) {
{duration6Id, duration6Index},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
-
+ EXPECT_EQ(replacedMetrics, set<int64_t>({duration2Id, duration3Id, duration4Id}));
// Make sure preserved metrics are the same.
ASSERT_EQ(newMetricProducers.size(), 5);
EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(duration1Id)],
@@ -2685,6 +2693,245 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) {
EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
}
+TEST_F(ConfigUpdateTest, TestUpdateValueMetrics) {
+ StatsdConfig config;
+
+ // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig.
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ AtomMatcher matcher4 = CreateTemperatureAtomMatcher();
+ int64_t matcher4Id = matcher4.id();
+ *config.add_atom_matcher() = matcher4;
+
+ AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+ int64_t matcher5Id = matcher5.id();
+ *config.add_atom_matcher() = matcher5;
+
+ Predicate predicate1 = CreateScreenIsOnPredicate();
+ int64_t predicate1Id = predicate1.id();
+ *config.add_predicate() = predicate1;
+
+ Predicate predicate2 = CreateScreenIsOffPredicate();
+ int64_t predicate2Id = predicate2.id();
+ *config.add_predicate() = predicate2;
+
+ State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321);
+ int64_t state1Id = state1.id();
+ *config.add_state() = state1;
+
+ State state2 = CreateScreenState();
+ int64_t state2Id = state2.id();
+ *config.add_state() = state2;
+
+ // Add a few value metrics.
+ // Note that these will not work as "real" metrics since the value field is always 2.
+ // Will be preserved.
+ ValueMetric value1 = createValueMetric("VALUE1", matcher4, predicate1Id, {state1Id});
+ int64_t value1Id = value1.id();
+ *config.add_value_metric() = value1;
+
+ // Will be replaced - definition change.
+ ValueMetric value2 = createValueMetric("VALUE2", matcher1, nullopt, {});
+ int64_t value2Id = value2.id();
+ *config.add_value_metric() = value2;
+
+ // Will be replaced - condition change.
+ ValueMetric value3 = createValueMetric("VALUE3", matcher5, predicate2Id, {});
+ int64_t value3Id = value3.id();
+ *config.add_value_metric() = value3;
+
+ // Will be replaced - state change.
+ ValueMetric value4 = createValueMetric("VALUE4", matcher3, nullopt, {state2Id});
+ int64_t value4Id = value4.id();
+ *config.add_value_metric() = value4;
+
+ // Will be deleted.
+ ValueMetric value5 = createValueMetric("VALUE5", matcher2, nullopt, {});
+ int64_t value5Id = value5.id();
+ *config.add_value_metric() = value5;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Used later to ensure the condition wizard is replaced. Get it before doing the update.
+ sp<EventMatcherWizard> oldMatcherWizard =
+ static_cast<ValueMetricProducer*>(oldMetricProducers[0].get())->mEventMatcherWizard;
+ EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6);
+
+ // Change value2, causing it to be replaced.
+ value2.set_aggregation_type(ValueMetric::AVG);
+
+ // Mark predicate 2 as replaced. Causes value3 to be replaced.
+ set<int64_t> replacedConditions = {predicate2Id};
+
+ // Mark state 2 as replaced. Causes value4 to be replaced.
+ set<int64_t> replacedStates = {state2Id};
+
+ // New value metric.
+ ValueMetric value6 = createValueMetric("VALUE6", matcher5, predicate1Id, {state1Id});
+ int64_t value6Id = value6.id();
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher5Index = 0;
+ newAtomMatchingTrackerMap[matcher5Id] = 0;
+ const int matcher4Index = 1;
+ newAtomMatchingTrackerMap[matcher4Id] = 1;
+ const int matcher3Index = 2;
+ newAtomMatchingTrackerMap[matcher3Id] = 2;
+ const int matcher2Index = 3;
+ newAtomMatchingTrackerMap[matcher2Id] = 3;
+ const int matcher1Index = 4;
+ newAtomMatchingTrackerMap[matcher1Id] = 4;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(5);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+
+ std::unordered_map<int64_t, int> newConditionTrackerMap;
+ const int predicate2Index = 0;
+ newConditionTrackerMap[predicate2Id] = 0;
+ const int predicate1Index = 1;
+ newConditionTrackerMap[predicate1Id] = 1;
+ // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<ConditionTracker>> newConditionTrackers(2);
+ std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+ newConditionTrackers.begin());
+ // Say that predicate1 & predicate2 is unknown since the initial condition never changed.
+ vector<ConditionState> conditionCache = {ConditionState::kUnknown, ConditionState::kUnknown};
+
+ StatsdConfig newConfig;
+ *newConfig.add_value_metric() = value6;
+ const int value6Index = 0;
+ *newConfig.add_value_metric() = value3;
+ const int value3Index = 1;
+ *newConfig.add_value_metric() = value1;
+ const int value1Index = 2;
+ *newConfig.add_value_metric() = value4;
+ const int value4Index = 3;
+ *newConfig.add_value_metric() = value2;
+ const int value2Index = 4;
+
+ *newConfig.add_state() = state1;
+ *newConfig.add_state() = state2;
+
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
+ map<int64_t, uint64_t> stateProtoHashes;
+ EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes));
+
+ // Output data structures to validate.
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
+ EXPECT_TRUE(updateMetrics(
+ key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
+ newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
+ newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
+ oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
+ conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation, replacedMetrics));
+
+ unordered_map<int64_t, int> expectedMetricProducerMap = {
+ {value1Id, value1Index}, {value2Id, value2Index}, {value3Id, value3Index},
+ {value4Id, value4Index}, {value6Id, value6Index},
+ };
+ EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({value2Id, value3Id, value4Id}));
+
+ // Make sure preserved metrics are the same.
+ ASSERT_EQ(newMetricProducers.size(), 5);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(value1Id)],
+ newMetricProducers[newMetricProducerMap.at(value1Id)]);
+
+ // Make sure replaced metrics are different.
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value2Id)],
+ newMetricProducers[newMetricProducerMap.at(value2Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value3Id)],
+ newMetricProducers[newMetricProducerMap.at(value3Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value4Id)],
+ newMetricProducers[newMetricProducerMap.at(value4Id)]);
+
+ // Verify the conditionToMetricMap.
+ ASSERT_EQ(conditionToMetricMap.size(), 2);
+ const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
+ EXPECT_THAT(condition1Metrics, UnorderedElementsAre(value1Index, value6Index));
+ const vector<int>& condition2Metrics = conditionToMetricMap[predicate2Index];
+ EXPECT_THAT(condition2Metrics, UnorderedElementsAre(value3Index));
+
+ // Verify the trackerToMetricMap.
+ ASSERT_EQ(trackerToMetricMap.size(), 4);
+ const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+ EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(value2Index));
+ const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(value4Index));
+ const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index];
+ EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(value1Index));
+ const vector<int>& matcher5Metrics = trackerToMetricMap[matcher5Index];
+ EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(value3Index, value6Index));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(metricsWithActivation.size(), 0);
+
+ // Verify tracker indices/ids/conditions/states are correct.
+ ValueMetricProducer* valueProducer1 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value1Index].get());
+ EXPECT_EQ(valueProducer1->getMetricId(), value1Id);
+ EXPECT_EQ(valueProducer1->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(valueProducer1->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(valueProducer1->mWhatMatcherIndex, matcher4Index);
+ ValueMetricProducer* valueProducer2 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value2Index].get());
+ EXPECT_EQ(valueProducer2->getMetricId(), value2Id);
+ EXPECT_EQ(valueProducer2->mConditionTrackerIndex, -1);
+ EXPECT_EQ(valueProducer2->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(valueProducer2->mWhatMatcherIndex, matcher1Index);
+ ValueMetricProducer* valueProducer3 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value3Index].get());
+ EXPECT_EQ(valueProducer3->getMetricId(), value3Id);
+ EXPECT_EQ(valueProducer3->mConditionTrackerIndex, predicate2Index);
+ EXPECT_EQ(valueProducer3->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(valueProducer3->mWhatMatcherIndex, matcher5Index);
+ ValueMetricProducer* valueProducer4 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value4Index].get());
+ EXPECT_EQ(valueProducer4->getMetricId(), value4Id);
+ EXPECT_EQ(valueProducer4->mConditionTrackerIndex, -1);
+ EXPECT_EQ(valueProducer4->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(valueProducer4->mWhatMatcherIndex, matcher3Index);
+ ValueMetricProducer* valueProducer6 =
+ static_cast<ValueMetricProducer*>(newMetricProducers[value6Index].get());
+ EXPECT_EQ(valueProducer6->getMetricId(), value6Id);
+ EXPECT_EQ(valueProducer6->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(valueProducer6->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(valueProducer6->mWhatMatcherIndex, matcher5Index);
+
+ sp<EventMatcherWizard> newMatcherWizard = valueProducer1->mEventMatcherWizard;
+ EXPECT_NE(newMatcherWizard, oldMatcherWizard);
+ EXPECT_EQ(newMatcherWizard->getStrongCount(), 6);
+ oldMetricProducers.clear();
+ // Only reference to the old wizard should be the one in the test.
+ EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1);
+}
+
TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) {
StatsdConfig config;
// Add atom matchers
@@ -2767,6 +3014,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) {
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2775,7 +3023,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) {
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
// Verify event activation/deactivation maps.
ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 3);
@@ -2850,6 +3098,11 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
*config.add_gauge_metric() = gaugeMetric;
// Preserved.
+ ValueMetric valueMetric = createValueMetric("VALUE1", matcher3, predicate1Id, {});
+ int64_t valueMetricId = valueMetric.id();
+ *config.add_value_metric() = valueMetric;
+
+ // Preserved.
DurationMetric durationMetric = createDurationMetric("DURATION1", predicate1Id, nullopt, {});
int64_t durationMetricId = durationMetric.id();
*config.add_duration_metric() = durationMetric;
@@ -2858,7 +3111,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
// Used later to ensure the condition wizard is replaced. Get it before doing the update.
sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard;
- EXPECT_EQ(oldConditionWizard->getStrongCount(), 5);
+ EXPECT_EQ(oldConditionWizard->getStrongCount(), 6);
// Mark matcher 2 as replaced. Causes eventMetric to be replaced.
set<int64_t> replacedMatchers;
@@ -2889,6 +3142,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
newConditionTrackers.begin());
vector<ConditionState> conditionCache = {ConditionState::kUnknown};
+ // The order matters. we parse in the order of: count, duration, event, value, gauge.
StatsdConfig newConfig;
*newConfig.add_count_metric() = countMetric;
const int countMetricIndex = 0;
@@ -2896,8 +3150,10 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
const int durationMetricIndex = 1;
*newConfig.add_event_metric() = eventMetric;
const int eventMetricIndex = 2;
+ *newConfig.add_value_metric() = valueMetric;
+ const int valueMetricIndex = 3;
*newConfig.add_gauge_metric() = gaugeMetric;
- const int gaugeMetricIndex = 3;
+ const int gaugeMetricIndex = 4;
// Add the predicate since duration metric needs it.
*newConfig.add_predicate() = predicate1;
@@ -2911,6 +3167,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
vector<int> metricsWithActivation;
+ set<int64_t> replacedMetrics;
EXPECT_TRUE(updateMetrics(
key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
@@ -2919,22 +3176,25 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
/*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation));
+ metricsWithActivation, replacedMetrics));
unordered_map<int64_t, int> expectedMetricProducerMap = {
- {countMetricId, countMetricIndex},
- {durationMetricId, durationMetricIndex},
- {eventMetricId, eventMetricIndex},
+ {countMetricId, countMetricIndex}, {durationMetricId, durationMetricIndex},
+ {eventMetricId, eventMetricIndex}, {valueMetricId, valueMetricIndex},
{gaugeMetricId, gaugeMetricIndex},
};
EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+ EXPECT_EQ(replacedMetrics, set<int64_t>({eventMetricId, gaugeMetricId}));
+
// Make sure preserved metrics are the same.
- ASSERT_EQ(newMetricProducers.size(), 4);
+ ASSERT_EQ(newMetricProducers.size(), 5);
EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)],
newMetricProducers[newMetricProducerMap.at(countMetricId)]);
EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(durationMetricId)],
newMetricProducers[newMetricProducerMap.at(durationMetricId)]);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(valueMetricId)],
+ newMetricProducers[newMetricProducerMap.at(valueMetricId)]);
// Make sure replaced metrics are different.
EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)],
@@ -2945,7 +3205,8 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
// Verify the conditionToMetricMap.
ASSERT_EQ(conditionToMetricMap.size(), 1);
const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
- EXPECT_THAT(condition1Metrics, UnorderedElementsAre(countMetricIndex, gaugeMetricIndex));
+ EXPECT_THAT(condition1Metrics,
+ UnorderedElementsAre(countMetricIndex, gaugeMetricIndex, valueMetricIndex));
// Verify the trackerToMetricMap.
ASSERT_EQ(trackerToMetricMap.size(), 3);
@@ -2954,7 +3215,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex, durationMetricIndex));
const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
- EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex));
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex, valueMetricIndex));
// Verify event activation/deactivation maps.
ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
@@ -2977,7 +3238,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard;
EXPECT_NE(newConditionWizard, oldConditionWizard);
- EXPECT_EQ(newConditionWizard->getStrongCount(), 5);
+ EXPECT_EQ(newConditionWizard->getStrongCount(), 6);
oldMetricProducers.clear();
// Only reference to the old wizard should be the one in the test.
EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f3b37891876d..3bcb87aa73f2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -865,6 +865,8 @@ public class AppOpsManager {
@UnsupportedAppUsage
public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS;
/** @hide */
+ public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS;
+ /** @hide */
@UnsupportedAppUsage
public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS;
/** @hide */
@@ -1155,7 +1157,7 @@ public class AppOpsManager {
/** @hide */
@UnsupportedAppUsage
- public static final int _NUM_OP = 103;
+ public static final int _NUM_OP = 104;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1466,6 +1468,15 @@ public class AppOpsManager {
public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats";
/**
+ * Grants an app access to the {@link android.telecom.InCallService} API to see
+ * information about ongoing calls and to enable control of calls.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
+
+ /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -1574,6 +1585,7 @@ public class AppOpsManager {
OP_MANAGE_EXTERNAL_STORAGE,
OP_INTERACT_ACROSS_PROFILES,
OP_LOADER_USAGE_STATS,
+ OP_MANAGE_ONGOING_CALLS,
};
/**
@@ -1688,6 +1700,7 @@ public class AppOpsManager {
OP_PHONE_CALL_MICROPHONE, // OP_PHONE_CALL_MICROPHONE
OP_PHONE_CALL_CAMERA, // OP_PHONE_CALL_CAMERA
OP_RECORD_AUDIO_HOTWORD, // RECORD_AUDIO_HOTWORD
+ OP_MANAGE_ONGOING_CALLS, // MANAGE_ONGOING_CALLS
};
/**
@@ -1797,6 +1810,7 @@ public class AppOpsManager {
OPSTR_PHONE_CALL_MICROPHONE,
OPSTR_PHONE_CALL_CAMERA,
OPSTR_RECORD_AUDIO_HOTWORD,
+ OPSTR_MANAGE_ONGOING_CALLS,
};
/**
@@ -1907,6 +1921,7 @@ public class AppOpsManager {
"PHONE_CALL_MICROPHONE",
"PHONE_CALL_CAMERA",
"RECORD_AUDIO_HOTWORD",
+ "MANAGE_ONGOING_CALLS",
};
/**
@@ -2018,6 +2033,7 @@ public class AppOpsManager {
null, // no permission for OP_PHONE_CALL_MICROPHONE
null, // no permission for OP_PHONE_CALL_CAMERA
null, // no permission for OP_RECORD_AUDIO_HOTWORD
+ Manifest.permission.MANAGE_ONGOING_CALLS,
};
/**
@@ -2129,6 +2145,7 @@ public class AppOpsManager {
null, // PHONE_CALL_MICROPHONE
null, // PHONE_CALL_MICROPHONE
null, // RECORD_AUDIO_HOTWORD
+ null, // MANAGE_ONGOING_CALLS
};
/**
@@ -2239,6 +2256,7 @@ public class AppOpsManager {
null, // PHONE_CALL_MICROPHONE
null, // PHONE_CALL_CAMERA
null, // RECORD_AUDIO_HOTWORD
+ null, // MANAGE_ONGOING_CALLS
};
/**
@@ -2348,6 +2366,7 @@ public class AppOpsManager {
AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD
+ AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS
};
/**
@@ -2461,6 +2480,7 @@ public class AppOpsManager {
false, // PHONE_CALL_MICROPHONE
false, // PHONE_CALL_CAMERA
false, // RECORD_AUDIO_HOTWORD
+ true, // MANAGE_ONGOING_CALLS
};
/**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 861e4378c68e..1f81c44e54cb 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -397,6 +397,7 @@ public final class PendingIntent implements Parcelable {
* parameters. May return null only if {@link #FLAG_NO_CREATE} has been
* supplied.
*/
+ @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
public static PendingIntent getActivity(Context context, int requestCode,
@NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
// Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null
@@ -528,6 +529,7 @@ public final class PendingIntent implements Parcelable {
* parameters. May return null only if {@link #FLAG_NO_CREATE} has been
* supplied.
*/
+ @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
public static PendingIntent getActivities(Context context, int requestCode,
@NonNull Intent[] intents, @Flags int flags, @Nullable Bundle options) {
// Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 849f679c9439..5caf3057c840 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -24,6 +24,8 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
@@ -181,6 +183,20 @@ public class TaskInfo {
public boolean isResizeable;
/**
+ * Activity bounds if this task or its top activity is presented in letterbox mode and
+ * {@code null} otherwise.
+ * @hide
+ */
+ @Nullable
+ public Rect letterboxActivityBounds;
+
+ /**
+ * Relative position of the task's top left corner in the parent container.
+ * @hide
+ */
+ public Point positionInParent;
+
+ /**
* The launch cookies associated with activities in this task if any.
* @see ActivityOptions#setLaunchCookie(IBinder)
* @hide
@@ -225,6 +241,7 @@ public class TaskInfo {
/** @hide */
public void addLaunchCookie(IBinder cookie) {
+ if (cookie == null || launchCookies.contains(cookie)) return;
launchCookies.add(cookie);
}
@@ -256,6 +273,8 @@ public class TaskInfo {
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
isResizeable = source.readBoolean();
source.readBinderList(launchCookies);
+ letterboxActivityBounds = source.readTypedObject(Rect.CREATOR);
+ positionInParent = source.readTypedObject(Point.CREATOR);
}
/**
@@ -287,6 +306,8 @@ public class TaskInfo {
dest.writeTypedObject(topActivityInfo, flags);
dest.writeBoolean(isResizeable);
dest.writeBinderList(launchCookies);
+ dest.writeTypedObject(letterboxActivityBounds, flags);
+ dest.writeTypedObject(positionInParent, flags);
}
@Override
@@ -306,6 +327,9 @@ public class TaskInfo {
+ " topActivityType=" + topActivityType
+ " pictureInPictureParams=" + pictureInPictureParams
+ " topActivityInfo=" + topActivityInfo
- + " launchCookies" + launchCookies;
+ + " launchCookies" + launchCookies
+ + " letterboxActivityBounds=" + letterboxActivityBounds
+ + " positionInParent=" + positionInParent
+ + "}";
}
}
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 4ae1670e9041..c4af4edb467b 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -796,6 +796,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/**
* Returns {@code true} if the windowingMode represents a window in multi-window mode.
* I.e. sharing the screen with another activity.
+ *
+ * TODO(b/171672645): This API could be misleading - in 'undefined' mode it's determined by the
+ * parent's mode
* @hide
*/
public static boolean inMultiWindowMode(int windowingMode) {
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 3cc7f1e5df42..4c541b3f6b76 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -55,22 +55,6 @@ public final class DeviceAdminInfo implements Parcelable {
static final String TAG = "DeviceAdminInfo";
/**
- * A type of policy that this device admin can use: device owner meta-policy
- * for an admin that is designated as owner of the device.
- *
- * @hide
- */
- public static final int USES_POLICY_DEVICE_OWNER = -2;
-
- /**
- * A type of policy that this device admin can use: profile owner meta-policy
- * for admins that have been installed as owner of some user profile.
- *
- * @hide
- */
- public static final int USES_POLICY_PROFILE_OWNER = -1;
-
- /**
* A type of policy that this device admin can use: limit the passwords
* that the user can select, via {@link DevicePolicyManager#setPasswordQuality}
* and {@link DevicePolicyManager#setPasswordMinimumLength}.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 054e8429ff86..ad902a028f13 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1830,15 +1830,6 @@ public class DevicePolicyManager {
public static final int STATE_USER_PROFILE_COMPLETE = 4;
/**
- * Management setup on a managed profile.
- * <p>This is used as an intermediate state after {@link #STATE_USER_PROFILE_COMPLETE} once the
- * work profile has been created.
- * @hide
- */
- @SystemApi
- public static final int STATE_USER_PROFILE_FINALIZED = 5;
-
- /**
* @hide
*/
@IntDef(prefix = { "STATE_USER_" }, value = {
@@ -1846,8 +1837,7 @@ public class DevicePolicyManager {
STATE_USER_SETUP_INCOMPLETE,
STATE_USER_SETUP_COMPLETE,
STATE_USER_SETUP_FINALIZED,
- STATE_USER_PROFILE_COMPLETE,
- STATE_USER_PROFILE_FINALIZED
+ STATE_USER_PROFILE_COMPLETE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserProvisioningState {}
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 802655b0d364..2b689899af01 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -46,7 +46,7 @@ public class BiometricTestSession implements AutoCloseable {
mContext = context;
mTestSession = testSession;
mTestedUsers = new ArraySet<>();
- enableTestHal(true);
+ setTestHalEnabled(true);
}
/**
@@ -56,12 +56,12 @@ public class BiometricTestSession implements AutoCloseable {
* secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
* equivalent for the secret key.
*
- * @param enableTestHal If true, enable testing with a fake HAL instead of the real HAL.
+ * @param enabled If true, enable testing with a fake HAL instead of the real HAL.
*/
@RequiresPermission(TEST_BIOMETRIC)
- private void enableTestHal(boolean enableTestHal) {
+ private void setTestHalEnabled(boolean enabled) {
try {
- mTestSession.enableTestHal(enableTestHal);
+ mTestSession.setTestHalEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -178,10 +178,12 @@ public class BiometricTestSession implements AutoCloseable {
@Override
@RequiresPermission(TEST_BIOMETRIC)
public void close() {
+ // Disable the test HAL first, so that enumerate is run on the real HAL, which should have
+ // no enrollments. Test-only framework enrollments will be deleted.
+ setTestHalEnabled(false);
+
for (int user : mTestedUsers) {
cleanupInternalState(user);
}
-
- enableTestHal(false);
}
}
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
index 6112f17949d7..fa7a62c53531 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -27,7 +27,7 @@ interface ITestSession {
// portion of the framework code that would otherwise require human interaction. Note that
// secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
// equivalent for the secret key.
- void enableTestHal(boolean enableTestHal);
+ void setTestHalEnabled(boolean enableTestHal);
// Starts the enrollment process. This should generally be used when the test HAL is enabled.
void startEnroll(int userId);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 8cfa0866f13a..228617c74388 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -190,6 +190,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
}
}
+ private final String mCameraId;
@UnsupportedAppUsage
private final CameraMetadataNative mResults;
private final CaptureRequest mRequest;
@@ -202,7 +203,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>For internal use only</p>
* @hide
*/
- public CaptureResult(CameraMetadataNative results, CaptureRequest parent,
+ public CaptureResult(String cameraId, CameraMetadataNative results, CaptureRequest parent,
CaptureResultExtras extras) {
if (results == null) {
throw new IllegalArgumentException("results was null");
@@ -221,6 +222,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
throw new AssertionError("Results must not be empty");
}
setNativeInstance(mResults);
+ mCameraId = cameraId;
mRequest = parent;
mSequenceId = extras.getRequestId();
mFrameNumber = extras.getFrameNumber();
@@ -251,12 +253,27 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
}
setNativeInstance(mResults);
+ mCameraId = "none";
mRequest = null;
mSequenceId = sequenceId;
mFrameNumber = -1;
}
/**
+ * Get the camera ID of the camera that produced this capture result.
+ *
+ * For a logical multi-camera, the ID may be the logical or the physical camera ID, depending on
+ * whether the capture result was obtained from
+ * {@link TotalCaptureResult#getPhysicalCameraResults} or not.
+ *
+ * @return The camera ID for the camera that produced this capture result.
+ */
+ @NonNull
+ public String getCameraId() {
+ return mCameraId;
+ }
+
+ /**
* Get a capture result field value.
*
* <p>The field definitions can be found in {@link CaptureResult}.</p>
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 7cc2623a29ba..da65f71ce02c 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -70,10 +70,10 @@ public final class TotalCaptureResult extends CaptureResult {
* @param partials a list of partial results; {@code null} will be substituted for an empty list
* @hide
*/
- public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
- CaptureResultExtras extras, List<CaptureResult> partials, int sessionId,
- PhysicalCaptureResultInfo physicalResults[]) {
- super(results, parent, extras);
+ public TotalCaptureResult(String logicalCameraId, CameraMetadataNative results,
+ CaptureRequest parent, CaptureResultExtras extras, List<CaptureResult> partials,
+ int sessionId, PhysicalCaptureResultInfo[] physicalResults) {
+ super(logicalCameraId, results, parent, extras);
if (partials == null) {
mPartialResults = new ArrayList<>();
@@ -85,7 +85,7 @@ public final class TotalCaptureResult extends CaptureResult {
mPhysicalCaptureResults = new HashMap<String, CaptureResult>();
for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
- CaptureResult physicalResult = new CaptureResult(
+ CaptureResult physicalResult = new CaptureResult(onePhysicalResult.getCameraId(),
onePhysicalResult.getCameraMetadata(), parent, extras);
mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
physicalResult);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 48ec3fd808fe..819d966e3bfe 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1980,7 +1980,7 @@ public class CameraDeviceImpl extends CameraDevice
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
- new CaptureResult(result, request, resultExtras);
+ new CaptureResult(getId(), result, request, resultExtras);
// Partial result
resultDispatch = new Runnable() {
@Override
@@ -1992,7 +1992,7 @@ public class CameraDeviceImpl extends CameraDevice
for (int i = 0; i < holder.getRequestCount(); i++) {
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
- CaptureResult resultInBatch = new CaptureResult(
+ CaptureResult resultInBatch = new CaptureResult(getId(),
resultLocal, holder.getRequest(i), resultExtras);
holder.getCallback().onCaptureProgressed(
@@ -2019,8 +2019,8 @@ public class CameraDeviceImpl extends CameraDevice
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
- final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
- request, resultExtras, partialResults, holder.getSessionId(),
+ final TotalCaptureResult resultAsCapture = new TotalCaptureResult(getId(),
+ result, request, resultExtras, partialResults, holder.getSessionId(),
physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@@ -2038,9 +2038,9 @@ public class CameraDeviceImpl extends CameraDevice
new CameraMetadataNative(resultCopy);
// No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
- resultLocal, holder.getRequest(i), resultExtras,
- partialResults, holder.getSessionId(),
- new PhysicalCaptureResultInfo[0]);
+ getId(), resultLocal, holder.getRequest(i),
+ resultExtras, partialResults, holder.getSessionId(),
+ new PhysicalCaptureResultInfo[0]);
holder.getCallback().onCaptureCompleted(
CameraDeviceImpl.this,
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 1d8b2a123c6a..eb2ff88ec1b2 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -334,7 +334,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
- new CaptureResult(result, request, resultExtras);
+ new CaptureResult(mCameraId, result, request, resultExtras);
// Partial result
resultDispatch = new Runnable() {
@Override
@@ -349,7 +349,8 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
final CaptureResult resultInBatch = new CaptureResult(
- resultLocal, holder.getRequest(i), resultExtras);
+ mCameraId, resultLocal, holder.getRequest(i),
+ resultExtras);
final CaptureRequest cbRequest = holder.getRequest(i);
callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
@@ -372,8 +373,8 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
- final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
- request, resultExtras, partialResults, holder.getSessionId(),
+ final TotalCaptureResult resultAsCapture = new TotalCaptureResult(mCameraId,
+ result, request, resultExtras, partialResults, holder.getSessionId(),
physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@@ -393,9 +394,9 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
new CameraMetadataNative(resultCopy);
// No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
- resultLocal, holder.getRequest(i), resultExtras,
- partialResults, holder.getSessionId(),
- new PhysicalCaptureResultInfo[0]);
+ mCameraId, resultLocal, holder.getRequest(i),
+ resultExtras, partialResults, holder.getSessionId(),
+ new PhysicalCaptureResultInfo[0]);
final CaptureRequest cbRequest = holder.getRequest(i);
callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index c39fd4d1bc43..8c98362fa909 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -44,7 +44,7 @@ import android.util.proto.ProtoOutputStream;
* public void run() {
* Looper.prepare();
*
- * mHandler = new Handler() {
+ * mHandler = new Handler(Looper.myLooper()) {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c3b6d8e2cfe3..105ffaa4718e 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -17,6 +17,7 @@
package android.provider;
+import android.annotation.LongDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -43,6 +44,8 @@ import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -611,6 +614,144 @@ public class CallLog {
*/
public static final String BLOCK_REASON = "block_reason";
+ /** @hide */
+ @LongDef(flag = true, value = {
+ MISSED_REASON_NOT_MISSED,
+ AUTO_MISSED_EMERGENCY_CALL,
+ AUTO_MISSED_MAXIMUM_RINGING,
+ AUTO_MISSED_MAXIMUM_DIALING,
+ USER_MISSED_NO_ANSWER,
+ USER_MISSED_SHORT_RING,
+ USER_MISSED_DND_MODE,
+ USER_MISSED_LOW_RING_VOLUME,
+ USER_MISSED_NO_VIBRATE,
+ USER_MISSED_CALL_SCREENING_SERVICE_SILENCED,
+ USER_MISSED_CALL_FILTERS_TIMEOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MissedReason {}
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set as the default value when a call was
+ * not missed.
+ */
+ public static final long MISSED_REASON_NOT_MISSED = 0;
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is
+ * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by
+ * system because an ongoing emergency call.
+ */
+ public static final long AUTO_MISSED_EMERGENCY_CALL = 1 << 0;
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is
+ * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by
+ * system because the system cannot support any more ringing calls.
+ */
+ public static final long AUTO_MISSED_MAXIMUM_RINGING = 1 << 1;
+
+ /**
+ * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is
+ * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by
+ * system because the system cannot support any more dialing calls.
+ */
+ public static final long AUTO_MISSED_MAXIMUM_DIALING = 1 << 2;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * the call was missed just because user didn't answer it.
+ */
+ public static final long USER_MISSED_NO_ANSWER = 1 << 16;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call rang for a short period of time.
+ */
+ public static final long USER_MISSED_SHORT_RING = 1 << 17;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, when this call
+ * rings less than this defined time in millisecond, set
+ * {@link CallLog.Calls#USER_MISSED_SHORT_RING} bit.
+ * @hide
+ */
+ public static final long SHORT_RING_THRESHOLD = 5000L;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call is silenced because the phone is in 'do not disturb mode'.
+ */
+ public static final long USER_MISSED_DND_MODE = 1 << 18;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call rings with a low ring volume.
+ */
+ public static final long USER_MISSED_LOW_RING_VOLUME = 1 << 19;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, when this call
+ * rings in volume less than this defined volume threshold, set
+ * {@link CallLog.Calls#USER_MISSED_LOW_RING_VOLUME} bit.
+ * @hide
+ */
+ public static final int LOW_RING_VOLUME = 0;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE} set this bit when
+ * this call rings without vibration.
+ */
+ public static final long USER_MISSED_NO_VIBRATE = 1 << 20;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * this call is silenced by the call screening service.
+ */
+ public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 1 << 21;
+
+ /**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * the call filters timed out.
+ */
+ public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22;
+
+ /**
+ * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE},
+ * indicates factors which may have lead the user to miss the call.
+ * <P>Type: INTEGER</P>
+ *
+ * <p>
+ * There are two main cases. Auto missed cases and user missed cases. Default value is:
+ * <ul>
+ * <li>{@link CallLog.Calls#MISSED_REASON_NOT_MISSED}</li>
+ * </ul>
+ * </p>
+ * <P>
+ * Auto missed cases are those where a call was missed because it was not possible for the
+ * incoming call to be presented to the user at all. Possible values are:
+ * <ul>
+ * <li>{@link CallLog.Calls#AUTO_MISSED_EMERGENCY_CALL}</li>
+ * <li>{@link CallLog.Calls#AUTO_MISSED_MAXIMUM_RINGING}</li>
+ * <li>{@link CallLog.Calls#AUTO_MISSED_MAXIMUM_DIALING}</li>
+ * </ul>
+ * </P>
+ * <P>
+ * User missed cases are those where the incoming call was presented to the user, but
+ * factors such as a low ringing volume may have contributed to the call being missed.
+ * Following bits can be set to indicate possible reasons for this:
+ * <ul>
+ * <li>{@link CallLog.Calls#USER_MISSED_SHORT_RING}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_DND_MODE}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_LOW_RING_VOLUME}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_NO_VIBRATE}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_CALL_SCREENING_SERVICE_SILENCED}</li>
+ * <li>{@link CallLog.Calls#USER_MISSED_CALL_FILTERS_TIMEOUT}</li>
+ * </ul>
+ * </P>
+ */
+ public static final String MISSED_REASON = "missed_reason";
+
/**
* Adds a call to the call log.
*
@@ -635,12 +776,13 @@ public class CallLog {
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, int features,
PhoneAccountHandle accountHandle,
- long start, int duration, Long dataUsage) {
+ long start, int duration, Long dataUsage, long missedReason) {
return addCall(ci, context, number, "" /* postDialDigits */, "" /* viaNumber */,
presentation, callType, features, accountHandle, start, duration,
dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */,
false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */,
- null /* callScreeningAppName */, null /* callScreeningComponentName */);
+ null /* callScreeningAppName */, null /* callScreeningComponentName */,
+ missedReason);
}
@@ -675,12 +817,13 @@ public class CallLog {
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, String viaNumber, int presentation, int callType,
int features, PhoneAccountHandle accountHandle, long start, int duration,
- Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo) {
+ Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
+ long missedReason) {
return addCall(ci, context, number, postDialDigits, viaNumber, presentation, callType,
features, accountHandle, start, duration, dataUsage, addForAllUsers,
userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED
/* callBlockReason */, null /* callScreeningAppName */,
- null /* callScreeningComponentName */);
+ null /* callScreeningComponentName */, missedReason);
}
/**
@@ -714,6 +857,7 @@ public class CallLog {
* @param callBlockReason The reason why the call is blocked.
* @param callScreeningAppName The call screening application name which block the call.
* @param callScreeningComponentName The call screening component name which block the call.
+ * @param missedReason The encoded missed information of the call.
*
* @result The URI of the call log entry belonging to the user that made or received this
* call. This could be of the shadow provider. Do not return it to non-system apps,
@@ -726,7 +870,7 @@ public class CallLog {
int features, PhoneAccountHandle accountHandle, long start, int duration,
Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
boolean isRead, int callBlockReason, CharSequence callScreeningAppName,
- String callScreeningComponentName) {
+ String callScreeningComponentName, long missedReason) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
number, userToBeInsertedTo, addForAllUsers));
@@ -779,6 +923,7 @@ public class CallLog {
values.put(BLOCK_REASON, callBlockReason);
values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName));
values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
+ values.put(MISSED_REASON, Long.valueOf(missedReason));
if ((ci != null) && (ci.getContactId() > 0)) {
// Update usage information for the number associated with the contact ID.
@@ -1114,5 +1259,19 @@ public class CallLog {
}
return countryIso;
}
+
+ /**
+ * Check if the missedReason code indicate that the call was user missed or automatically
+ * rejected by system.
+ *
+ * @param missedReason
+ * The result is true if the call was user missed, false if the call was automatically
+ * rejected by system.
+ *
+ * @hide
+ */
+ public static boolean isUserMissed(long missedReason) {
+ return missedReason >= (USER_MISSED_NO_ANSWER);
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a4c8114159d3..60d1d477db53 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8380,18 +8380,19 @@ public final class Settings {
public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled";
/**
- * Whether the panic button (emergency sos) gesture should be enabled.
+ * Whether the emergency gesture should be enabled.
*
* @hide
*/
- public static final String PANIC_GESTURE_ENABLED = "panic_gesture_enabled";
+ public static final String EMERGENCY_GESTURE_ENABLED = "emergency_gesture_enabled";
/**
- * Whether the panic button (emergency sos) sound should be enabled.
+ * Whether the emergency gesture sound should be enabled.
*
* @hide
*/
- public static final String PANIC_SOUND_ENABLED = "panic_sound_enabled";
+ public static final String EMERGENCY_GESTURE_SOUND_ENABLED =
+ "emergency_gesture_sound_enabled";
/**
* Whether the camera launch gesture to double tap the power button when the screen is off
@@ -9914,13 +9915,19 @@ public final class Settings {
"hdmi_control_auto_device_off_enabled";
/**
- * Property to decide which devices the playback device can send a <Standby> message to upon
- * going to sleep. Supported values are:
+ * Property to decide which devices the playback device can send a <Standby> message to
+ * upon going to sleep. It additionally controls whether a playback device attempts to turn
+ * on the connected Audio system when waking up. Supported values are:
* <ul>
- * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_TO_TV} to TV only.</li>
- * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_BROADCAST} to all devices in the
- * network.</li>
- * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_NONE} no <Standby> message sent.</li>
+ * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_TO_TV} Upon going to sleep, device
+ * sends {@code <Standby>} to TV only. Upon waking up, device does not turn on the Audio
+ * system via {@code <System Audio Mode Request>}.</li>
+ * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_BROADCAST} Upon going to sleep,
+ * device sends {@code <Standby>} to all devices in the network. Upon waking up, device
+ * attempts to turn on the Audio system via {@code <System Audio Mode Request>}.</li>
+ * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_NONE} Upon going to sleep, device
+ * does not send any {@code <Standby>} message. Upon waking up, device does not turn on the
+ * Audio system via {@code <System Audio Mode Request>}.</li>
* </ul>
*
* @hide
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 6bd376a19fc5..c5277ee0ed5e 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -206,8 +206,8 @@ public abstract class ControlsProviderService extends Service {
case MSG_SUBSCRIBE: {
final SubscribeMessage sMsg = (SubscribeMessage) msg.obj;
- final SubscriberProxy proxy = new SubscriberProxy(false, mToken,
- sMsg.mSubscriber);
+ final SubscriberProxy proxy = new SubscriberProxy(
+ ControlsProviderService.this, false, mToken, sMsg.mSubscriber);
ControlsProviderService.this.createPublisherFor(sMsg.mControlIds)
.subscribe(proxy);
@@ -251,6 +251,7 @@ public abstract class ControlsProviderService extends Service {
private IBinder mToken;
private IControlsSubscriber mCs;
private boolean mEnforceStateless;
+ private Context mContext;
SubscriberProxy(boolean enforceStateless, IBinder token, IControlsSubscriber cs) {
mEnforceStateless = enforceStateless;
@@ -258,6 +259,12 @@ public abstract class ControlsProviderService extends Service {
mCs = cs;
}
+ SubscriberProxy(Context context, boolean enforceStateless, IBinder token,
+ IControlsSubscriber cs) {
+ this(enforceStateless, token, cs);
+ mContext = context;
+ }
+
public void onSubscribe(Subscription subscription) {
try {
mCs.onSubscribe(mToken, new SubscriptionAdapter(subscription));
@@ -273,6 +280,9 @@ public abstract class ControlsProviderService extends Service {
+ "Control.StatelessBuilder() to build the control.");
control = new Control.StatelessBuilder(control).build();
}
+ if (mContext != null) {
+ control.getControlTemplate().prepareTemplateForBinder(mContext);
+ }
mCs.onNext(mToken, control);
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java
index e592fad394b8..3902d6af69e7 100644
--- a/core/java/android/service/controls/templates/ControlTemplate.java
+++ b/core/java/android/service/controls/templates/ControlTemplate.java
@@ -20,6 +20,7 @@ import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.Bundle;
import android.service.controls.Control;
import android.service.controls.actions.ControlAction;
@@ -78,6 +79,7 @@ public abstract class ControlTemplate {
TYPE_NO_TEMPLATE,
TYPE_TOGGLE,
TYPE_RANGE,
+ TYPE_THUMBNAIL,
TYPE_TOGGLE_RANGE,
TYPE_TEMPERATURE,
TYPE_STATELESS
@@ -105,6 +107,11 @@ public abstract class ControlTemplate {
public static final @TemplateType int TYPE_RANGE = 2;
/**
+ * Type identifier of {@link ThumbnailTemplate}.
+ */
+ public static final @TemplateType int TYPE_THUMBNAIL = 3;
+
+ /**
* Type identifier of {@link ToggleRangeTemplate}.
*/
public static final @TemplateType int TYPE_TOGGLE_RANGE = 6;
@@ -169,6 +176,13 @@ public abstract class ControlTemplate {
}
/**
+ * Call to prepare values for Binder transport.
+ *
+ * @hide
+ */
+ public void prepareTemplateForBinder(@NonNull Context context) {}
+
+ /**
*
* @param bundle
* @return
@@ -187,6 +201,8 @@ public abstract class ControlTemplate {
return new ToggleTemplate(bundle);
case TYPE_RANGE:
return new RangeTemplate(bundle);
+ case TYPE_THUMBNAIL:
+ return new ThumbnailTemplate(bundle);
case TYPE_TOGGLE_RANGE:
return new ToggleRangeTemplate(bundle);
case TYPE_TEMPERATURE:
diff --git a/core/java/android/service/controls/templates/ThumbnailTemplate.java b/core/java/android/service/controls/templates/ThumbnailTemplate.java
new file mode 100644
index 000000000000..a7c481e38e55
--- /dev/null
+++ b/core/java/android/service/controls/templates/ThumbnailTemplate.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.controls.templates;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.service.controls.Control;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+/**
+ * A template for a {@link Control} that displays an image.
+ */
+public final class ThumbnailTemplate extends ControlTemplate {
+
+ private static final @TemplateType int TYPE = TYPE_THUMBNAIL;
+ private static final String KEY_ICON = "key_icon";
+ private static final String KEY_ACTIVE = "key_active";
+ private static final String KEY_CONTENT_DESCRIPTION = "key_content_description";
+
+ private final boolean mActive;
+ private final @NonNull Icon mThumbnail;
+ private final @NonNull CharSequence mContentDescription;
+
+ /**
+ * @param templateId the identifier for this template object
+ * @param active whether the image corresponds to an active (live) stream.
+ * @param thumbnail an image to display on the {@link Control}
+ * @param contentDescription a description of the image for accessibility.
+ */
+ public ThumbnailTemplate(
+ @NonNull String templateId,
+ boolean active,
+ @NonNull Icon thumbnail,
+ @NonNull CharSequence contentDescription) {
+ super(templateId);
+ Preconditions.checkNotNull(thumbnail);
+ Preconditions.checkNotNull(contentDescription);
+ mActive = active;
+ mThumbnail = thumbnail;
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * @param b
+ * @hide
+ */
+ ThumbnailTemplate(Bundle b) {
+ super(b);
+ mActive = b.getBoolean(KEY_ACTIVE);
+ mThumbnail = b.getParcelable(KEY_ICON);
+ mContentDescription = b.getCharSequence(KEY_CONTENT_DESCRIPTION, "");
+ }
+
+ /*
+ * @return {@code true} if the thumbnail corresponds to an active (live) stream.
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * The {@link Icon} (image) displayed by this template.
+ */
+ @NonNull
+ public Icon getThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * The description of the image returned by {@link ThumbnailTemplate#getThumbnail()}
+ */
+ @NonNull
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * @return {@link ControlTemplate#TYPE_THUMBNAIL}
+ */
+ @Override
+ public int getTemplateType() {
+ return TYPE;
+ }
+
+ /**
+ * Rescales the image down if necessary (in the case of a Bitmap).
+ *
+ * @hide
+ */
+ @Override
+ public void prepareTemplateForBinder(@NonNull Context context) {
+ int width = context.getResources()
+ .getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_width);
+ int height = context.getResources()
+ .getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_height);
+ rescaleThumbnail(width, height);
+ }
+
+ private void rescaleThumbnail(int width, int height) {
+ mThumbnail.scaleDownIfNecessary(width, height);
+ }
+
+ /**
+ * @return
+ * @hide
+ */
+ @Override
+ @NonNull
+ Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putBoolean(KEY_ACTIVE, mActive);
+ b.putObject(KEY_ICON, mThumbnail);
+ b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription);
+ return b;
+ }
+}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index ee98b65d2444..a7d20b56eca3 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -64,7 +64,7 @@ public class EventLog {
private Exception mLastWtf;
// Layout of event log entry received from Android logger.
- // see system/core/liblog/include/log/log_read.h
+ // see system/logging/liblog/include/log/log_read.h
private static final int LENGTH_OFFSET = 0;
private static final int HEADER_SIZE_OFFSET = 2;
private static final int PROCESS_OFFSET = 4;
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 12ea936e96a1..726176568605 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -44,24 +43,16 @@ import java.util.ArrayList;
public class NotificationHeaderView extends ViewGroup {
private final int mChildMinWidth;
private final int mContentEndMargin;
- private final int mGravity;
- private View mAppName;
- private View mHeaderText;
- private View mSecondaryHeaderText;
private OnClickListener mExpandClickListener;
- private OnClickListener mFeedbackListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private NotificationTopLineView mTopLineView;
private NotificationExpandButton mExpandButton;
private CachingIconView mIcon;
- private View mProfileBadge;
- private View mFeedbackIcon;
- private boolean mShowWorkBadgeAtEnd;
private int mHeaderTextMarginEnd;
private Drawable mBackground;
private boolean mEntireHeaderClickable;
private boolean mExpandOnlyOnButton;
private boolean mAcceptAllTouches;
- private int mTotalWidth;
ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
@@ -93,23 +84,14 @@ public class NotificationHeaderView extends ViewGroup {
mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
-
- int[] attrIds = {android.R.attr.gravity};
- TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
- mGravity = ta.getInt(0, 0);
- ta.recycle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mAppName = findViewById(R.id.app_name_text);
- mHeaderText = findViewById(R.id.header_text);
- mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
- mExpandButton = findViewById(R.id.expand_button);
mIcon = findViewById(R.id.icon);
- mProfileBadge = findViewById(R.id.profile_badge);
- mFeedbackIcon = findViewById(R.id.feedback);
+ mTopLineView = findViewById(R.id.notification_top_line);
+ mExpandButton = findViewById(R.id.expand_button);
setClipToPadding(false);
}
@@ -136,7 +118,7 @@ public class NotificationHeaderView extends ViewGroup {
lp.topMargin + lp.bottomMargin, lp.height);
child.measure(childWidthSpec, childHeightSpec);
// Icons that should go at the end
- if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) {
+ if (child == mExpandButton) {
iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
} else {
totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -147,19 +129,10 @@ public class NotificationHeaderView extends ViewGroup {
int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth);
if (totalWidth > givenWidth - endMargin) {
int overFlow = totalWidth - givenWidth + endMargin;
- // We are overflowing, lets shrink the app name first
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
+ // We are overflowing; shrink the top line
+ shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mTopLineView,
mChildMinWidth);
-
- // still overflowing, we shrink the header text
- overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
-
- // still overflowing, finally we shrink the secondary header text
- shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
- 0);
}
- totalWidth += getPaddingEnd();
- mTotalWidth = Math.min(totalWidth, givenWidth);
setMeasuredDimension(givenWidth, givenHeight);
}
@@ -180,10 +153,6 @@ public class NotificationHeaderView extends ViewGroup {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingStart();
int end = getMeasuredWidth();
- final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
- if (centerAligned) {
- left += getMeasuredWidth() / 2 - mTotalWidth / 2;
- }
int childCount = getChildCount();
int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
for (int i = 0; i < childCount; i++) {
@@ -198,7 +167,7 @@ public class NotificationHeaderView extends ViewGroup {
int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
int bottom = top + childHeight;
// Icons that should go at the end
- if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) {
+ if (child == mExpandButton) {
if (end == getMeasuredWidth()) {
layoutRight = end - mContentEndMargin;
} else {
@@ -266,7 +235,7 @@ public class NotificationHeaderView extends ViewGroup {
}
private void updateTouchListener() {
- if (mExpandClickListener == null && mFeedbackListener == null) {
+ if (mExpandClickListener == null) {
setOnTouchListener(null);
return;
}
@@ -274,15 +243,6 @@ public class NotificationHeaderView extends ViewGroup {
mTouchListener.bindTouchRects();
}
- /**
- * Sets onclick listener for feedback icon.
- */
- public void setFeedbackOnClickListener(OnClickListener l) {
- mFeedbackListener = l;
- mFeedbackIcon.setOnClickListener(mFeedbackListener);
- updateTouchListener();
- }
-
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
mExpandClickListener = l;
@@ -291,16 +251,6 @@ public class NotificationHeaderView extends ViewGroup {
}
/**
- * Sets whether or not the work badge appears at the end of the NotificationHeaderView.
- * The expand button will always be closer to the end.
- */
- public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
- if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
- mShowWorkBadgeAtEnd = showWorkBadgeAtEnd;
- }
- }
-
- /**
* Sets the margin end for the text portion of the header, excluding right-aligned elements
* @param headerTextMarginEnd margin size
*/
@@ -327,7 +277,6 @@ public class NotificationHeaderView extends ViewGroup {
private final ArrayList<Rect> mTouchRects = new ArrayList<>();
private Rect mExpandButtonRect;
- private Rect mFeedbackRect;
private int mTouchSlop;
private boolean mTrackGesture;
private float mDownX;
@@ -340,7 +289,6 @@ public class NotificationHeaderView extends ViewGroup {
mTouchRects.clear();
addRectAroundView(mIcon);
mExpandButtonRect = addRectAroundView(mExpandButton);
- mFeedbackRect = addRectAroundView(mFeedbackIcon);
addWidthRect();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -401,11 +349,11 @@ public class NotificationHeaderView extends ViewGroup {
break;
case MotionEvent.ACTION_UP:
if (mTrackGesture) {
- if (mFeedbackIcon.isVisibleToUser()
- && (mFeedbackRect.contains((int) x, (int) y)
- || mFeedbackRect.contains((int) mDownX, (int) mDownY))) {
- mFeedbackIcon.performClick();
- return true;
+ float topLineX = mTopLineView.getX();
+ float topLineY = mTopLineView.getY();
+ if (mTopLineView.onTouchUp(x - topLineX, y - topLineY,
+ mDownX - topLineX, mDownY - topLineY)) {
+ break;
}
mExpandButton.performClick();
}
@@ -427,7 +375,9 @@ public class NotificationHeaderView extends ViewGroup {
return true;
}
}
- return false;
+ float topLineX = x - mTopLineView.getX();
+ float topLineY = y - mTopLineView.getY();
+ return mTopLineView.isInTouchRect(topLineX, topLineY);
}
}
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
new file mode 100644
index 000000000000..9443ccfc7553
--- /dev/null
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 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.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The top line of content in a notification view.
+ * This includes the text views and badges but excludes the icon and the expander.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationTopLineView extends ViewGroup {
+ private final int mChildMinWidth;
+ private final int mContentEndMargin;
+ private View mAppName;
+ private View mHeaderText;
+ private View mSecondaryHeaderText;
+ private OnClickListener mFeedbackListener;
+ private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private View mProfileBadge;
+ private View mFeedbackIcon;
+ private int mHeaderTextMarginEnd;
+ private List<View> mIconsAtEnd;
+
+ public NotificationTopLineView(Context context) {
+ this(context, null);
+ }
+
+ public NotificationTopLineView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationTopLineView(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationTopLineView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ Resources res = getResources();
+ mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppName = findViewById(R.id.app_name_text);
+ mHeaderText = findViewById(R.id.header_text);
+ mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+ mProfileBadge = findViewById(R.id.profile_badge);
+ mFeedbackIcon = findViewById(R.id.feedback);
+ mIconsAtEnd = Arrays.asList(mProfileBadge, mFeedbackIcon);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
+ MeasureSpec.AT_MOST);
+ int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
+ MeasureSpec.AT_MOST);
+ int totalWidth = getPaddingStart();
+ int iconWidth = getPaddingEnd();
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ // We'll give it the rest of the space in the end
+ continue;
+ }
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+ lp.leftMargin + lp.rightMargin, lp.width);
+ int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+ lp.topMargin + lp.bottomMargin, lp.height);
+ child.measure(childWidthSpec, childHeightSpec);
+ // Icons that should go at the end
+ if (mIconsAtEnd.contains(child)) {
+ iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ } else {
+ totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ }
+ }
+
+ // Ensure that there is at least enough space for the icons
+ int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth);
+ if (totalWidth > givenWidth - endMargin) {
+ int overFlow = totalWidth - givenWidth + endMargin;
+ // We are overflowing, lets shrink the app name first
+ overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
+ mChildMinWidth);
+
+ // still overflowing, we shrink the header text
+ overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
+
+ // still overflowing, finally we shrink the secondary header text
+ shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
+ 0);
+ }
+ setMeasuredDimension(givenWidth, givenHeight);
+ }
+
+ private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
+ int minimumWidth) {
+ final int oldWidth = targetView.getMeasuredWidth();
+ if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
+ // we're still too big
+ int newSize = Math.max(minimumWidth, oldWidth - overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ targetView.measure(childWidthSpec, heightSpec);
+ overFlow -= oldWidth - newSize;
+ }
+ return overFlow;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int left = getPaddingStart();
+ int end = getMeasuredWidth();
+ int childCount = getChildCount();
+ int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ int childHeight = child.getMeasuredHeight();
+ MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
+ int layoutLeft;
+ int layoutRight;
+ int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
+ int bottom = top + childHeight;
+ // Icons that should go at the end
+ if (mIconsAtEnd.contains(child)) {
+ if (end == getMeasuredWidth()) {
+ layoutRight = end - mContentEndMargin;
+ } else {
+ layoutRight = end - params.getMarginEnd();
+ }
+ layoutLeft = layoutRight - child.getMeasuredWidth();
+ end = layoutLeft - params.getMarginStart();
+ } else {
+ left += params.getMarginStart();
+ int right = left + child.getMeasuredWidth();
+ layoutLeft = left;
+ layoutRight = right;
+ left = right + params.getMarginEnd();
+ }
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ int ltrLeft = layoutLeft;
+ layoutLeft = getWidth() - layoutRight;
+ layoutRight = getWidth() - ltrLeft;
+ }
+ child.layout(layoutLeft, top, layoutRight, bottom);
+ }
+ updateTouchListener();
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+ private void updateTouchListener() {
+ if (mFeedbackListener == null) {
+ setOnTouchListener(null);
+ return;
+ }
+ setOnTouchListener(mTouchListener);
+ mTouchListener.bindTouchRects();
+ }
+
+ /**
+ * Sets onclick listener for feedback icon.
+ */
+ public void setFeedbackOnClickListener(OnClickListener l) {
+ mFeedbackListener = l;
+ mFeedbackIcon.setOnClickListener(mFeedbackListener);
+ updateTouchListener();
+ }
+
+ /**
+ * Sets the margin end for the text portion of the header, excluding right-aligned elements
+ *
+ * @param headerTextMarginEnd margin size
+ */
+ public void setHeaderTextMarginEnd(int headerTextMarginEnd) {
+ if (mHeaderTextMarginEnd != headerTextMarginEnd) {
+ mHeaderTextMarginEnd = headerTextMarginEnd;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Get the current margin end value for the header text
+ *
+ * @return margin size
+ */
+ public int getHeaderTextMarginEnd() {
+ return mHeaderTextMarginEnd;
+ }
+
+ private class HeaderTouchListener implements OnTouchListener {
+
+ private Rect mFeedbackRect;
+ private int mTouchSlop;
+ private boolean mTrackGesture;
+ private float mDownX;
+ private float mDownY;
+
+ HeaderTouchListener() {
+ }
+
+ public void bindTouchRects() {
+ mFeedbackRect = getRectAroundView(mFeedbackIcon);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ private Rect getRectAroundView(View view) {
+ float size = 48 * getResources().getDisplayMetrics().density;
+ float width = Math.max(size, view.getWidth());
+ float height = Math.max(size, view.getHeight());
+ final Rect r = new Rect();
+ if (view.getVisibility() == GONE) {
+ view = getFirstChildNotGone();
+ r.left = (int) (view.getLeft() - width / 2.0f);
+ } else {
+ r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f);
+ }
+ r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f);
+ r.bottom = (int) (r.top + height);
+ r.right = (int) (r.left + width);
+ return r;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mTrackGesture = false;
+ if (isInside(x, y)) {
+ mDownX = x;
+ mDownY = y;
+ mTrackGesture = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackGesture) {
+ if (Math.abs(mDownX - x) > mTouchSlop
+ || Math.abs(mDownY - y) > mTouchSlop) {
+ mTrackGesture = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackGesture && onTouchUp(x, y, mDownX, mDownY)) {
+ return true;
+ }
+ break;
+ }
+ return mTrackGesture;
+ }
+
+ private boolean onTouchUp(float upX, float upY, float downX, float downY) {
+ if (mFeedbackIcon.isVisibleToUser()
+ && (mFeedbackRect.contains((int) upX, (int) upY)
+ || mFeedbackRect.contains((int) downX, (int) downY))) {
+ mFeedbackIcon.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInside(float x, float y) {
+ return mFeedbackRect.contains((int) x, (int) y);
+ }
+ }
+
+ private View getFirstChildNotGone() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ return child;
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /**
+ * Determine if the given point is touching an active part of the top line.
+ */
+ public boolean isInTouchRect(float x, float y) {
+ if (mFeedbackListener == null) {
+ return false;
+ }
+ return mTouchListener.isInside(x, y);
+ }
+
+ /**
+ * Perform a click on an active part of the top line, if touching.
+ */
+ public boolean onTouchUp(float upX, float upY, float downX, float downY) {
+ if (mFeedbackListener == null) {
+ return false;
+ }
+ return mTouchListener.onTouchUp(upX, upY, downX, downY);
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 667f0b9511d0..bda368ecb3a2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -80,7 +80,6 @@ import android.graphics.drawable.GradientDrawable;
import android.hardware.display.DisplayManagerGlobal;
import android.net.Uri;
import android.os.Build;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -26444,7 +26443,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
surface.copyFrom(surfaceControl);
IBinder token = null;
try {
- final Canvas canvas = surface.lockCanvas(null);
+ final Canvas canvas = isHardwareAccelerated()
+ ? surface.lockHardwareCanvas()
+ : surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
@@ -26535,7 +26536,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if (mAttachInfo.mDragToken != null) {
try {
- Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ Canvas canvas = isHardwareAccelerated()
+ ? mAttachInfo.mDragSurface.lockHardwareCanvas()
+ : mAttachInfo.mDragSurface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
@@ -28913,33 +28916,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean mUse32BitDrawingCache;
/**
- * For windows that are full-screen but using insets to layout inside
- * of the screen decorations, these are the current insets for the
- * content of the window.
- */
- @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q,
- publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
- final Rect mContentInsets = new Rect();
-
- /**
- * For windows that are full-screen but using insets to layout inside
- * of the screen decorations, these are the current insets for the
- * actual visible parts of the window.
- */
- @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q,
- publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
- final Rect mVisibleInsets = new Rect();
-
- /**
- * For windows that are full-screen but using insets to layout inside
- * of the screen decorations, these are the current insets for the
- * stable system windows.
- */
- @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q,
- publicAlternatives = "Use {@link WindowInsets#getInsets(int)}")
- final Rect mStableInsets = new Rect();
-
- /**
* Current caption insets to the display coordinate.
*/
final Rect mCaptionInsets = new Rect();
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 3a84c1f98ce6..4a43a438b69d 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -40,7 +40,7 @@ interface ITaskOrganizerController {
void unregisterTaskOrganizer(ITaskOrganizer organizer);
/** Creates a persistent root task in WM for a particular windowing-mode. */
- ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode);
+ void createRootTask(int displayId, int windowingMode, IBinder launchCookie);
/** Deletes a persistent root task in WM */
boolean deleteRootTask(in WindowContainerToken task);
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 6c739bed35a4..ad48a9f9f776 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.app.ActivityManager;
+import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
@@ -101,12 +102,18 @@ public class TaskOrganizer extends WindowOrganizer {
@BinderThread
public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
- /** Creates a persistent root task in WM for a particular windowing-mode. */
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ * @param displayId The display to create the root task on.
+ * @param windowingMode Windowing mode to put the root task in.
+ * @param launchCookie Launch cookie to associate with the task so that is can be identified
+ * when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
+ */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
@Nullable
- public ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode) {
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
try {
- return mTaskOrganizerController.createRootTask(displayId, windowingMode);
+ mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto
index 37a9f50dec89..2d2c8accc039 100644
--- a/core/proto/android/app/enums.proto
+++ b/core/proto/android/app/enums.proto
@@ -210,4 +210,5 @@ enum AppOpEnum {
APP_OP_PHONE_CALL_MICROPHONE = 100;
APP_OP_PHONE_CALL_CAMERA = 101;
APP_OP_RECORD_AUDIO_HOTWORD = 102;
+ APP_OP_MANAGE_ONGOING_CALLS = 103;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 83c53915694a..d934b82e36c3 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -219,8 +219,10 @@ message SecureSettingsProto {
optional SettingProto emergency_assistance_application = 22 [ (android.privacy).dest = DEST_AUTOMATIC ];
message EmergencyResponse {
- optional SettingProto panic_gesture_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto panic_sound_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto emergency_gesture_enabled = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto emergency_gesture_sound_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ reserved 1,2;
}
optional EmergencyResponse emergency_response = 83;
diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto
index a264f18f921c..a49a1adcc619 100644
--- a/core/proto/android/server/fingerprint.proto
+++ b/core/proto/android/server/fingerprint.proto
@@ -66,3 +66,36 @@ message PerformanceStatsProto {
// Total number of permanent lockouts.
optional int32 permanent_lockout = 5;
}
+
+// Internal FingerprintService states. The above messages (FingerprintServiceDumpProto, etc)
+// are used for legacy metrics and should not be modified.
+message FingerprintServiceStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ repeated SensorStateProto sensor_states = 1;
+}
+
+// State of a single sensor.
+message SensorStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Unique sensorId
+ optional int32 sensor_id = 1;
+
+ // State of the sensor's scheduler. True if currently handling an operation, false if idle.
+ optional bool is_busy = 2;
+
+ // User states for this sensor.
+ repeated UserStateProto user_states = 3;
+}
+
+// State of a specific user for a specific sensor.
+message UserStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Android user ID
+ optional int32 user_id = 1;
+
+ // Number of fingerprints enrolled
+ optional int32 num_enrolled = 2;
+} \ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 01954510ee08..8a7fa04b3cf4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2229,6 +2229,11 @@
<permission android:name="android.permission.BIND_INCALL_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Allows to query ongoing call details and manage ongoing calls
+ <p>Protection level: signature|appop -->
+ <permission android:name="android.permission.MANAGE_ONGOING_CALLS"
+ android:protectionLevel="signature|appop" />
+
<!-- Allows the app to request network scans from telephony.
<p>Not for use by third-party applications.
@SystemApi @hide-->
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 88493c9505ed..d11875e0f890 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -38,81 +38,7 @@
android:layout_gravity="center"
/>
</FrameLayout>
- <TextView
- android:id="@+id/app_name_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_app_name_margin_start"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:visibility="?attr/notificationHeaderAppNameVisibility"
- android:singleLine="true"
- />
- <TextView
- android:id="@+id/header_text_secondary_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:visibility="gone"/>
- <TextView
- android:id="@+id/header_text_secondary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:visibility="gone"
- android:singleLine="true"/>
- <TextView
- android:id="@+id/header_text_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:visibility="gone"/>
- <TextView
- android:id="@+id/header_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:visibility="gone"
- android:singleLine="true"/>
- <TextView
- android:id="@+id/time_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"/>
- <DateTimeView
- android:id="@+id/time"
- android:textAppearance="@style/TextAppearance.Material.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:showRelative="true"
- android:singleLine="true"
- android:visibility="gone" />
- <ViewStub
- android:id="@+id/chronometer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:layout="@layout/notification_template_part_chronometer"
- android:visibility="gone"
- />
+ <include layout="@layout/notification_template_top_line" />
<com.android.internal.widget.NotificationExpandButton
android:id="@+id/expand_button"
android:background="@null"
@@ -123,42 +49,5 @@
android:visibility="gone"
android:contentDescription="@string/expand_button_content_description_collapsed"
/>
- <ImageView
- android:id="@+id/alerted_icon"
- android:layout_width="@dimen/notification_alerted_size"
- android:layout_height="@dimen/notification_alerted_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="1dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_alerted_content_description"
- android:src="@drawable/ic_notifications_alerted"
- />
- <ImageButton
- android:id="@+id/feedback"
- android:layout_width="@dimen/notification_feedback_size"
- android:layout_height="@dimen/notification_feedback_size"
- android:layout_marginStart="6dp"
- android:layout_marginEnd="6dp"
- android:paddingTop="2dp"
- android:layout_gravity="center"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_feedback_indicator"
- android:background="?android:selectableItemBackgroundBorderless"
- android:visibility="gone"
- android:contentDescription="@string/notification_feedback_indicator"
- />
- <ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="1dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_top_line.xml b/core/res/res/layout/notification_template_top_line.xml
new file mode 100644
index 000000000000..27fab859a045
--- /dev/null
+++ b/core/res/res/layout/notification_template_top_line.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<!-- extends ViewGroup -->
+<NotificationTopLineView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ >
+ <TextView
+ android:id="@+id/app_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_app_name_margin_start"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="?attr/notificationHeaderAppNameVisibility"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/header_text_secondary_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_text_secondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="gone"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/header_text_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:visibility="gone"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"/>
+ <DateTimeView
+ android:id="@+id/time"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:showRelative="true"
+ android:singleLine="true"
+ android:visibility="gone" />
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@dimen/notification_alerted_size"
+ android:layout_height="@dimen/notification_alerted_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:src="@drawable/ic_notifications_alerted"
+ />
+ <ImageButton
+ android:id="@+id/feedback"
+ android:layout_width="@dimen/notification_feedback_size"
+ android:layout_height="@dimen/notification_feedback_size"
+ android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ android:paddingTop="2dp"
+ android:layout_gravity="center"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_feedback_indicator"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_feedback_indicator"
+ />
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
+ />
+</NotificationTopLineView>
+
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e6ebd4b543c5..84e7d42f2ed9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -207,6 +207,9 @@
<!-- Default padding for dialogs. -->
<dimen name="dialog_padding">16dp</dimen>
+ <!-- The horizontal margin of the content in the notification shade -->
+ <dimen name="notification_shade_content_margin_horizontal">16dp</dimen>
+
<!-- The margin on the start of the content view -->
<dimen name="notification_content_margin_start">16dp</dimen>
@@ -857,4 +860,9 @@
<dimen name="waterfall_display_top_edge_size">0px</dimen>
<dimen name="waterfall_display_right_edge_size">0px</dimen>
<dimen name="waterfall_display_bottom_edge_size">0px</dimen>
+
+ <!-- The maximum height of a thumbnail in a ThumbnailTemplate. The image will be reduced to that height in case they are bigger. -->
+ <dimen name="controls_thumbnail_image_max_height">140dp</dimen>
+ <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
+ <dimen name="controls_thumbnail_image_max_width">280dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 94397d661af0..697f7e0e898a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2852,6 +2852,7 @@
<java-symbol type="id" name="header_text_secondary" />
<java-symbol type="id" name="expand_button" />
<java-symbol type="id" name="notification_header" />
+ <java-symbol type="id" name="notification_top_line" />
<java-symbol type="id" name="time_divider" />
<java-symbol type="id" name="header_text_divider" />
<java-symbol type="id" name="header_text_secondary_divider" />
@@ -2862,6 +2863,7 @@
<java-symbol type="drawable" name="ic_collapse_bundle" />
<java-symbol type="dimen" name="notification_min_content_height" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_shade_content_margin_horizontal" />
<java-symbol type="dimen" name="notification_content_margin_start" />
<java-symbol type="dimen" name="notification_content_margin_end" />
<java-symbol type="dimen" name="notification_reply_inset" />
@@ -4079,4 +4081,7 @@
<java-symbol type="array" name="config_keep_warming_services" />
<java-symbol type="string" name="config_display_features" />
<java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" />
+
+ <java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
+ <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
</resources>
diff --git a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
index f4ebe2f9a755..63e464240630 100644
--- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
+++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
@@ -32,6 +32,8 @@ import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -39,11 +41,14 @@ import android.os.RemoteException;
import android.service.controls.actions.CommandAction;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.ControlActionWrapper;
+import android.service.controls.templates.ThumbnailTemplate;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -107,7 +112,8 @@ public class ControlProviderServiceTest {
mPendingIntent = new PendingIntent(mIIntentSender);
- mControlsProviderService = new FakeControlsProviderService();
+ mControlsProviderService = new FakeControlsProviderService(
+ InstrumentationRegistry.getInstrumentation().getContext());
mControlsProvider = IControlsProvider.Stub.asInterface(
mControlsProviderService.onBind(intent));
}
@@ -134,7 +140,8 @@ public class ControlProviderServiceTest {
verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
subscriptionCaptor.getValue().request(1000);
- verify(mSubscriber, times(2)).onNext(eq(mToken), controlCaptor.capture());
+ verify(mSubscriber, times(2))
+ .onNext(eq(mToken), controlCaptor.capture());
List<Control> values = controlCaptor.getAllValues();
assertTrue(equals(values.get(0), list.get(0)));
assertTrue(equals(values.get(1), list.get(1)));
@@ -210,26 +217,69 @@ public class ControlProviderServiceTest {
.setStatus(Control.STATUS_OK)
.build();
- @SuppressWarnings("unchecked")
- ArgumentCaptor<Control> controlCaptor =
- ArgumentCaptor.forClass(Control.class);
- ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor =
- ArgumentCaptor.forClass(IControlsSubscription.Stub.class);
+ Control c = sendControlGetControl(control);
+ assertTrue(equals(c, control));
+ }
- ArrayList<Control> list = new ArrayList<>();
- list.add(control);
+ @Test
+ public void testThumbnailRescaled_bigger() throws RemoteException {
+ Context context = mControlsProviderService.getBaseContext();
+ int maxWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_width);
+ int maxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_height);
- mControlsProviderService.setControls(list);
+ int min = Math.min(maxWidth, maxHeight);
+ int max = Math.max(maxWidth, maxHeight);
- mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ Bitmap bitmap = Bitmap.createBitmap(max * 2, max * 2, Bitmap.Config.ALPHA_8);
+ Icon icon = Icon.createWithBitmap(bitmap);
+ ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, "");
- verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
- subscriptionCaptor.getValue().request(1);
+ Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
+ .setTitle("TEST_TITLE")
+ .setStatus(Control.STATUS_OK)
+ .setControlTemplate(template)
+ .build();
- verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture());
- Control c = controlCaptor.getValue();
- assertTrue(equals(c, list.get(0)));
+ Control c = sendControlGetControl(control);
+
+ ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate();
+ Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap();
+
+ // Aspect ratio is kept
+ assertEquals(sentBitmap.getWidth(), sentBitmap.getHeight());
+
+ assertEquals(min, sentBitmap.getWidth());
+ }
+
+ @Test
+ public void testThumbnailRescaled_smaller() throws RemoteException {
+ Context context = mControlsProviderService.getBaseContext();
+ int maxWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_width);
+ int maxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.controls_thumbnail_image_max_height);
+
+ int min = Math.min(maxWidth, maxHeight);
+
+ Bitmap bitmap = Bitmap.createBitmap(min / 2, min / 2, Bitmap.Config.ALPHA_8);
+ Icon icon = Icon.createWithBitmap(bitmap);
+ ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, "");
+
+ Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent)
+ .setTitle("TEST_TITLE")
+ .setStatus(Control.STATUS_OK)
+ .setControlTemplate(template)
+ .build();
+
+ Control c = sendControlGetControl(control);
+
+ ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate();
+ Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap();
+
+ assertEquals(bitmap.getHeight(), sentBitmap.getHeight());
+ assertEquals(bitmap.getWidth(), sentBitmap.getWidth());
}
@Test
@@ -257,6 +307,32 @@ public class ControlProviderServiceTest {
intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL)));
}
+ /**
+ * Sends the control through the publisher in {@code mControlsProviderService}, returning
+ * the control obtained by the subscriber
+ */
+ private Control sendControlGetControl(Control control) throws RemoteException {
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<Control> controlCaptor =
+ ArgumentCaptor.forClass(Control.class);
+ ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor =
+ ArgumentCaptor.forClass(IControlsSubscription.Stub.class);
+
+ ArrayList<Control> list = new ArrayList<>();
+ list.add(control);
+
+ mControlsProviderService.setControls(list);
+
+ mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture());
+ subscriptionCaptor.getValue().request(1);
+
+ verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture());
+ return controlCaptor.getValue();
+ }
+
private static boolean equals(Control c1, Control c2) {
if (c1 == c2) return true;
if (c1 == null || c2 == null) return false;
@@ -276,6 +352,11 @@ public class ControlProviderServiceTest {
static class FakeControlsProviderService extends ControlsProviderService {
+ FakeControlsProviderService(Context context) {
+ super();
+ attachBaseContext(context);
+ }
+
private List<Control> mControls;
public void setControls(List<Control> controls) {
diff --git a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
index 87dc1b7c83d5..91a3ba7d0e74 100644
--- a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
+++ b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
@@ -103,6 +103,17 @@ public class ControlTemplateTest {
}
@Test
+ public void testUnparcelingCorrectClass_thumbnail() {
+ ControlTemplate toParcel = new ThumbnailTemplate(TEST_ID, false, mIcon,
+ TEST_ACTION_DESCRIPTION);
+
+ ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+ assertEquals(ControlTemplate.TYPE_THUMBNAIL, fromParcel.getTemplateType());
+ assertTrue(fromParcel instanceof ThumbnailTemplate);
+ }
+
+ @Test
public void testUnparcelingCorrectClass_toggleRange() {
ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton,
new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"));
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index a430dcdda247..e51bf5e4e1f6 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2923,6 +2923,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskDisplayArea.java"
},
+ "1396893178": {
+ "message": "createRootTask unknown displayId=%d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+ },
"1401295262": {
"message": "Mode default, asking user",
"level": "WARN",
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index f8db4477d87a..a4e69de97299 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -37,6 +37,18 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-1325223370": {
+ "message": "Task appeared taskId=%d listener=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
+ "-1312360667": {
+ "message": "createRootTask() displayId=%d winMode=%d listener=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
"-1006733970": {
"message": "Display added: %d",
"level": "VERBOSE",
@@ -55,6 +67,18 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-848099324": {
+ "message": "Letterbox Task Appeared: #%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
+ },
+ "-842742255": {
+ "message": "%s onTaskAppeared unknown taskId=%d winMode=%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
+ },
"-712674749": {
"message": "Clip description: %s",
"level": "VERBOSE",
@@ -67,11 +91,11 @@
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
},
- "-460572385": {
- "message": "Task appeared taskId=%d",
+ "-679492476": {
+ "message": "%s onTaskAppeared Primary taskId=%d",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
},
"-191422040": {
"message": "Transition animations finished, notifying core %s",
@@ -79,6 +103,12 @@
"group": "WM_SHELL_TRANSITIONS",
"at": "com\/android\/wm\/shell\/Transitions.java"
},
+ "154313206": {
+ "message": "%s onTaskAppeared Secondary taskId=%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
+ },
"157713005": {
"message": "Task info changed taskId=%d",
"level": "VERBOSE",
@@ -115,12 +145,24 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "1104702476": {
+ "message": "Letterbox Task Changed: #%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
+ },
"1184615936": {
"message": "Set drop target window visibility: displayId=%d visibility=%d",
"level": "VERBOSE",
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
},
+ "1218010718": {
+ "message": "Letterbox Task Vanished: #%d",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
+ },
"1481772149": {
"message": "Current target: %s",
"level": "VERBOSE",
@@ -144,6 +186,12 @@
"level": "VERBOSE",
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
+ "2135461748": {
+ "message": "%s onTaskAppeared Supported",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
}
},
"groups": {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 5bd693a9311e..fc0a76e8d286 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -20,9 +20,7 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCR
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
import android.app.ActivityManager;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.SurfaceControl;
@@ -39,7 +37,7 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private final SyncTransactionQueue mSyncQueue;
- private final ArrayMap<Integer, SurfaceControl> mTasks = new ArrayMap<>();
+ private final ArraySet<Integer> mTasks = new ArraySet<>();
FullscreenTaskListener(SyncTransactionQueue syncQueue) {
mSyncQueue = syncQueue;
@@ -48,17 +46,17 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mTasks) {
- if (mTasks.containsKey(taskInfo.taskId)) {
+ if (mTasks.contains(taskInfo.taskId)) {
throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
taskInfo.taskId);
- mTasks.put(taskInfo.taskId, leash);
+ mTasks.add(taskInfo.taskId);
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
- updateSurfacePosition(t, taskInfo, leash);
t.setWindowCrop(leash, null);
+ t.setPosition(leash, 0, 0);
// TODO(shell-transitions): Eventually set everything in transition so there's no
// SF Transaction here.
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -73,7 +71,7 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
synchronized (mTasks) {
- if (mTasks.remove(taskInfo.taskId) == null) {
+ if (!mTasks.remove(taskInfo.taskId)) {
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
return;
}
@@ -83,23 +81,6 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- synchronized (mTasks) {
- if (!mTasks.containsKey(taskInfo.taskId)) {
- Slog.e(TAG, "Changed Task wasn't appeared or already vanished: #"
- + taskInfo.taskId);
- return;
- }
- final SurfaceControl leash = mTasks.get(taskInfo.taskId);
- mSyncQueue.runInSync(t -> {
- // Reposition the task in case the bounds has been changed (such as Task level
- // letterboxing).
- updateSurfacePosition(t, taskInfo, leash);
- });
- }
- }
-
- @Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
@@ -112,12 +93,4 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
- /** Places the Task surface to the latest position. */
- private static void updateSurfacePosition(SurfaceControl.Transaction t,
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- // TODO(170725334) drop this after ag/12876439
- final Configuration config = taskInfo.getConfiguration();
- final Rect bounds = config.windowConfiguration.getBounds();
- t.setPosition(leash, bounds.left, bounds.top);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java
new file mode 100644
index 000000000000..9010c2088c34
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when
+ * it's presented in the letterbox mode either because orientations of a top activity and a device
+ * don't match or because a top activity is in a size compat mode.
+ */
+final class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = "LetterboxTaskListener";
+
+ private final SyncTransactionQueue mSyncQueue;
+
+ private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
+
+ LetterboxTaskListener(SyncTransactionQueue syncQueue) {
+ mSyncQueue = syncQueue;
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ synchronized (mLeashByTaskId) {
+ if (mLeashByTaskId.get(taskInfo.taskId) != null) {
+ throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d",
+ taskInfo.taskId);
+ mLeashByTaskId.put(taskInfo.taskId, leash);
+ final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
+ final Rect activtyBounds = taskInfo.letterboxActivityBounds;
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ setPositionAndWindowCrop(
+ t, leash, activtyBounds, taskBounds, taskPositionInParent);
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ synchronized (mLeashByTaskId) {
+ if (mLeashByTaskId.get(taskInfo.taskId) == null) {
+ Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+ return;
+ }
+ mLeashByTaskId.remove(taskInfo.taskId);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d",
+ taskInfo.taskId);
+ }
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ synchronized (mLeashByTaskId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d",
+ taskInfo.taskId);
+ final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
+ final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
+ final Rect activtyBounds = taskInfo.letterboxActivityBounds;
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ setPositionAndWindowCrop(
+ t, leash, activtyBounds, taskBounds, taskPositionInParent);
+ });
+ }
+ }
+
+ private static void setPositionAndWindowCrop(
+ SurfaceControl.Transaction transaction,
+ SurfaceControl leash,
+ final Rect activityBounds,
+ final Rect taskBounds,
+ final Point taskPositionInParent) {
+ Rect activtyInTaskCoordinates = new Rect(activityBounds);
+ activtyInTaskCoordinates.offset(-taskBounds.left, -taskBounds.top);
+ transaction.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ transaction.setWindowCrop(leash, activtyInTaskCoordinates);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index cbc1c8d6d310..d4ff275d426d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -20,8 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
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 com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
@@ -29,6 +27,7 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
import android.annotation.IntDef;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
+import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.Log;
@@ -45,7 +44,6 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -64,14 +62,14 @@ public class ShellTaskOrganizer extends TaskOrganizer {
public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
public static final int TASK_LISTENER_TYPE_PIP = -4;
- public static final int TASK_LISTENER_TYPE_SPLIT_SCREEN = -5;
+ public static final int TASK_LISTENER_TYPE_LETTERBOX = -5;
@IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
TASK_LISTENER_TYPE_UNDEFINED,
TASK_LISTENER_TYPE_FULLSCREEN,
TASK_LISTENER_TYPE_MULTI_WINDOW,
TASK_LISTENER_TYPE_PIP,
- TASK_LISTENER_TYPE_SPLIT_SCREEN,
+ TASK_LISTENER_TYPE_LETTERBOX,
})
public @interface TaskListenerType {}
@@ -118,6 +116,7 @@ public class ShellTaskOrganizer extends TaskOrganizer {
ShellExecutor mainExecutor, ShellExecutor animExecutor) {
super(taskOrganizerController, mainExecutor);
addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN);
+ addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX);
mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
}
@@ -137,6 +136,14 @@ public class ShellTaskOrganizer extends TaskOrganizer {
}
}
+ public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s",
+ displayId, windowingMode, listener.toString());
+ final IBinder cookie = new Binder();
+ setPendingLaunchCookieListener(cookie, listener);
+ super.createRootTask(displayId, windowingMode, cookie);
+ }
+
/**
* Adds a listener for a specific task id.
*/
@@ -236,10 +243,10 @@ public class ShellTaskOrganizer extends TaskOrganizer {
private void onTaskAppeared(TaskAppearedInfo info) {
final int taskId = info.getTaskInfo().taskId;
- ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d", taskId);
mTasks.put(taskId, info);
final TaskListener listener =
getTaskListener(info.getTaskInfo(), true /*removeLaunchCookieIfNeeded*/);
+ ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d listener=%s", taskId, listener);
if (listener != null) {
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
@@ -329,26 +336,25 @@ public class ShellTaskOrganizer extends TaskOrganizer {
if (listener != null) return listener;
// Next we try type specific listeners.
- final int windowingMode = getWindowingMode(runningTaskInfo);
- final int taskListenerType = windowingModeToTaskListenerType(windowingMode);
+ final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo);
return mTaskListeners.get(taskListenerType);
}
@WindowingMode
- private static int getWindowingMode(RunningTaskInfo taskInfo) {
+ public static int getWindowingMode(RunningTaskInfo taskInfo) {
return taskInfo.configuration.windowConfiguration.getWindowingMode();
}
- private static @TaskListenerType int windowingModeToTaskListenerType(
- @WindowingMode int windowingMode) {
+ @VisibleForTesting
+ static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) {
+ final int windowingMode = getWindowingMode(runningTaskInfo);
switch (windowingMode) {
case WINDOWING_MODE_FULLSCREEN:
- return TASK_LISTENER_TYPE_FULLSCREEN;
+ return runningTaskInfo.letterboxActivityBounds != null
+ ? TASK_LISTENER_TYPE_LETTERBOX
+ : TASK_LISTENER_TYPE_FULLSCREEN;
case WINDOWING_MODE_MULTI_WINDOW:
return TASK_LISTENER_TYPE_MULTI_WINDOW;
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
- case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
- return TASK_LISTENER_TYPE_SPLIT_SCREEN;
case WINDOWING_MODE_PINNED:
return TASK_LISTENER_TYPE_PIP;
case WINDOWING_MODE_FREEFORM:
@@ -362,10 +368,10 @@ public class ShellTaskOrganizer extends TaskOrganizer {
switch (type) {
case TASK_LISTENER_TYPE_FULLSCREEN:
return "TASK_LISTENER_TYPE_FULLSCREEN";
+ case TASK_LISTENER_TYPE_LETTERBOX:
+ return "TASK_LISTENER_TYPE_LETTERBOX";
case TASK_LISTENER_TYPE_MULTI_WINDOW:
return "TASK_LISTENER_TYPE_MULTI_WINDOW";
- case TASK_LISTENER_TYPE_SPLIT_SCREEN:
- return "TASK_LISTENER_TYPE_SPLIT_SCREEN";
case TASK_LISTENER_TYPE_PIP:
return "TASK_LISTENER_TYPE_PIP";
case TASK_LISTENER_TYPE_UNDEFINED:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 9b4524a12161..a05aac9dfe3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -772,6 +772,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (animator == null || !animator.isRunning()
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
if (mState.isInPip() && fromRotation) {
+ // Update bounds state to final destination first. It's important to do this
+ // before finishing & cancelling the transition animation so that the MotionHelper
+ // bounds are synchronized to the destination bounds when the animation ends.
+ mPipBoundsState.setBounds(destinationBoundsOut);
// If we are rotating while there is a current animation, immediately cancel the
// animation (remove the listeners so we don't trigger the normal finish resize
// call that should only happen on the update thread)
@@ -785,7 +789,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
sendOnPipTransitionCancelled(direction);
sendOnPipTransitionFinished(direction);
}
- mPipBoundsState.setBounds(destinationBoundsOut);
// Create a reset surface transaction for the new bounds and update the window
// container transaction
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java
index ff617ed466d1..eb82357f2dea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java
@@ -40,7 +40,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor
private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
- private final SplitScreenTaskOrganizer mSplits;
+ private final SplitScreenTaskListener mSplits;
private final TransactionPool mTransactionPool;
private final Handler mHandler;
private final TaskOrganizer mTaskOrganizer;
@@ -92,7 +92,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor
private boolean mPausedTargetAdjusted = false;
private boolean mAdjustedWhileHidden = false;
- DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler,
+ DividerImeController(SplitScreenTaskListener splits, TransactionPool pool, Handler handler,
TaskOrganizer taskOrganizer) {
mSplits = splits;
mTransactionPool = pool;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
index 2b14e8bf88d6..c6496ad6246f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
@@ -155,7 +155,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private boolean mAdjustedForIme;
private DividerState mState;
- private SplitScreenTaskOrganizer mTiles;
+ private SplitScreenTaskListener mTiles;
boolean mFirstLayout = true;
int mDividerPositionX;
int mDividerPositionY;
@@ -354,7 +354,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
void injectDependencies(SplitScreenController splitScreenController,
DividerWindowManager windowManager, DividerState dividerState,
- DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl,
+ DividerCallbacks callback, SplitScreenTaskListener tiles, SplitDisplayLayout sdl,
DividerImeController imeController, WindowManagerProxy wmProxy) {
mSplitScreenController = splitScreenController;
mWindowManager = windowManager;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java
index 3c0f93906795..7d5e1a84a38d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java
@@ -47,7 +47,7 @@ public class SplitDisplayLayout {
private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
- SplitScreenTaskOrganizer mTiles;
+ SplitScreenTaskListener mTiles;
DisplayLayout mDisplayLayout;
Context mContext;
@@ -62,7 +62,7 @@ public class SplitDisplayLayout {
Rect mAdjustedPrimary = null;
Rect mAdjustedSecondary = null;
- public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskOrganizer taskTiles) {
+ public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskListener taskTiles) {
mTiles = taskTiles;
mDisplayLayout = dl;
mContext = ctx;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 43e4d62baaf6..69d428a3ae1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -75,7 +75,7 @@ public class SplitScreenController implements SplitScreen,
private final DividerState mDividerState = new DividerState();
private final ForcedResizableInfoActivityController mForcedResizableController;
private final Handler mHandler;
- private final SplitScreenTaskOrganizer mSplits;
+ private final SplitScreenTaskListener mSplits;
private final SystemWindows mSystemWindows;
final TransactionPool mTransactionPool;
private final WindowManagerProxy mWindowManagerProxy;
@@ -117,7 +117,7 @@ public class SplitScreenController implements SplitScreen,
mTransactionPool = transactionPool;
mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
mTaskOrganizer = shellTaskOrganizer;
- mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer);
+ mSplits = new SplitScreenTaskListener(this, shellTaskOrganizer);
mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler,
shellTaskOrganizer);
mRotationController =
@@ -164,6 +164,14 @@ public class SplitScreenController implements SplitScreen,
// Don't initialize the divider or anything until we get the default display.
}
+ void onSplitScreenSupported() {
+ // Set starting tile bounds based on middle target
+ final WindowContainerTransaction tct = new WindowContainerTransaction();
+ int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+ mSplitLayout.resizeSplits(midPos, tct);
+ mTaskOrganizer.applyTransaction(tct);
+ }
+
@Override
public boolean isSplitScreenSupported() {
return mSplits.isSplitScreenSupported();
@@ -196,11 +204,6 @@ public class SplitScreenController implements SplitScreen,
}
try {
mSplits.init();
- // Set starting tile bounds based on middle target
- final WindowContainerTransaction tct = new WindowContainerTransaction();
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- mSplitLayout.resizeSplits(midPos, tct);
- mTaskOrganizer.applyTransaction(tct);
} catch (Exception e) {
Slog.e(TAG, "Failed to register docked stack listener", e);
removeDivider();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
index f763d6d714c4..191a317452e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
@@ -23,25 +23,24 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_SPLIT_SCREEN;
-import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+import static com.android.wm.shell.ShellTaskOrganizer.getWindowingMode;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager.RunningTaskInfo;
import android.graphics.Rect;
-import android.os.RemoteException;
import android.util.Log;
-import android.view.Display;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import java.io.PrintWriter;
-class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = "SplitScreenTaskOrg";
+class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = "SplitScreenTaskListener";
private static final boolean DEBUG = SplitScreenController.DEBUG;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -58,20 +57,19 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
final SurfaceSession mSurfaceSession = new SurfaceSession();
- SplitScreenTaskOrganizer(SplitScreenController splitScreenController,
+ SplitScreenTaskListener(SplitScreenController splitScreenController,
ShellTaskOrganizer shellTaskOrganizer) {
mSplitScreenController = splitScreenController;
mTaskOrganizer = shellTaskOrganizer;
- mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_SPLIT_SCREEN);
}
- void init() throws RemoteException {
+ void init() {
synchronized (this) {
try {
- mPrimary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- mSecondary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mTaskOrganizer.createRootTask(
+ DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this);
+ mTaskOrganizer.createRootTask(
+ DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this);
} catch (Exception e) {
// teardown to prevent callbacks
mTaskOrganizer.removeListener(this);
@@ -95,19 +93,26 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (this) {
- if (mPrimary == null || mSecondary == null) {
- Log.w(TAG, "Received onTaskAppeared before creating root tasks " + taskInfo);
- return;
- }
-
- if (taskInfo.token.equals(mPrimary.token)) {
+ final int winMode = getWindowingMode(taskInfo);
+ if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ ProtoLog.v(WM_SHELL_TASK_ORG,
+ "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId);
+ mPrimary = taskInfo;
mPrimarySurface = leash;
- } else if (taskInfo.token.equals(mSecondary.token)) {
+ } else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ ProtoLog.v(WM_SHELL_TASK_ORG,
+ "%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId);
+ mSecondary = taskInfo;
mSecondarySurface = leash;
+ } else {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d",
+ TAG, taskInfo.taskId, winMode);
}
if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
mSplitScreenSupported = true;
+ mSplitScreenController.onSplitScreenSupported();
+ ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG);
// Initialize dim surfaces:
mPrimaryDim = new SurfaceControl.Builder(mSurfaceSession)
@@ -240,10 +245,13 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
+ pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported);
+ if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId);
+ if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId);
}
@Override
public String toString() {
- return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_SPLIT_SCREEN);
+ return TAG;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
index 47e7c99d2268..c51bbeb7b6c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
@@ -88,7 +88,7 @@ class WindowManagerProxy {
mTaskOrganizer = taskOrganizer;
}
- void dismissOrMaximizeDocked(final SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
+ void dismissOrMaximizeDocked(final SplitScreenTaskListener tiles, SplitDisplayLayout layout,
final boolean dismissOrMaximize) {
mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize));
}
@@ -189,7 +189,7 @@ class WindowManagerProxy {
*
* @return whether the home stack is resizable
*/
- boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
+ boolean applyEnterSplit(SplitScreenTaskListener tiles, SplitDisplayLayout layout) {
// Set launchtile first so that any stack created after
// getAllRootTaskInfos and before reparent (even if unlikely) are placed
// correctly.
@@ -242,7 +242,7 @@ class WindowManagerProxy {
* split (thus resulting in the top of the secondary split becoming
* fullscreen. {@code false} resolves the other way.
*/
- void applyDismissSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
+ void applyDismissSplit(SplitScreenTaskListener tiles, SplitDisplayLayout layout,
boolean dismissOrMaximize) {
// Set launch root first so that any task created after getChildContainers and
// before reparent (pretty unlikely) are put into fullscreen.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java
new file mode 100644
index 000000000000..45d4d5d347dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxTaskListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LetterboxTaskListenerTest {
+
+ private static final Rect ACTIVITY_BOUNDS = new Rect(300, 200, 700, 400);
+ private static final Rect TASK_BOUNDS = new Rect(200, 100, 800, 500);
+ private static final Rect TASK_BOUNDS_2 = new Rect(300, 200, 800, 500);
+ private static final Point TASK_POSITION_IN_PARENT = new Point(100, 50);
+ private static final Point TASK_POSITION_IN_PARENT_2 = new Point(200, 100);
+
+ private static final Rect EXPECTED_WINDOW_CROP = new Rect(100, 100, 500, 300);
+ private static final Rect EXPECTED_WINDOW_CROP_2 = new Rect(0, 0, 400, 200);
+
+ private static final RunningTaskInfo TASK_INFO = createTaskInfo(
+ /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS, TASK_POSITION_IN_PARENT);
+
+ private static final RunningTaskInfo TASK_INFO_2 = createTaskInfo(
+ /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS_2, TASK_POSITION_IN_PARENT_2);
+
+ @Mock private SurfaceControl mLeash;
+ @Mock private SurfaceControl.Transaction mTransaction;
+ private LetterboxTaskListener mLetterboxTaskListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLetterboxTaskListener = new LetterboxTaskListener(
+ new SyncTransactionQueue(
+ new TransactionPool() {
+ @Override
+ public SurfaceControl.Transaction acquire() {
+ return mTransaction;
+ }
+
+ @Override
+ public void release(SurfaceControl.Transaction t) {
+ }
+ },
+ new Handler(Looper.getMainLooper())));
+ }
+
+ @Test
+ public void testOnTaskAppearedAndonTaskInfoChanged_setCorrectPositionAndCrop() {
+ mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
+
+ verify(mTransaction).setPosition(
+ eq(mLeash),
+ eq((float) TASK_POSITION_IN_PARENT.x),
+ eq((float) TASK_POSITION_IN_PARENT.y));
+ // Should return activty coordinates offset by task coordinates
+ verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP));
+
+ mLetterboxTaskListener.onTaskInfoChanged(TASK_INFO_2);
+
+ verify(mTransaction).setPosition(
+ eq(mLeash),
+ eq((float) TASK_POSITION_IN_PARENT_2.x),
+ eq((float) TASK_POSITION_IN_PARENT_2.y));
+ // Should return activty coordinates offset by task coordinates
+ verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP_2));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
+ mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
+ mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
+ }
+
+ private static RunningTaskInfo createTaskInfo(
+ int taskId,
+ final Rect activityBounds,
+ final Rect taskBounds,
+ final Point taskPositionInParent) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
+ taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
+ taskInfo.positionInParent = new Point(taskPositionInParent);
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 07a6bda239c7..35a2293cbf13 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,15 +16,18 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERBOX;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -34,6 +37,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.pm.ParceledListSlice;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -42,6 +46,7 @@ import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -242,10 +247,41 @@ public class ShellTaskOrganizerTests {
assertTrue(gotException);
}
- private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ @Test
+ public void testTaskInfoToTaskListenerType_whenLetterboxBoundsPassed_returnsLetterboxType() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId */ 1,
+ WINDOWING_MODE_FULLSCREEN,
+ /* letterboxActivityBounds */ new Rect(1, 1, 1, 1));
+
+ assertEquals(
+ ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo),
+ TASK_LISTENER_TYPE_LETTERBOX);
+ }
+
+ @Test
+ public void testTaskInfoToTaskListenerType_whenLetterboxBoundsIsNull_returnsFullscreenType() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId */ 1, WINDOWING_MODE_FULLSCREEN, /* letterboxActivityBounds */ null);
+
+ assertEquals(
+ ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo),
+ TASK_LISTENER_TYPE_FULLSCREEN);
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ return taskInfo;
+ }
+
+ private static RunningTaskInfo createTaskInfo(
+ int taskId, int windowingMode, @Nullable Rect letterboxActivityBounds) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ taskInfo.letterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds);
return taskInfo;
}
}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 42cf53b44f1e..3905e0b2b878 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -52,9 +52,11 @@ interface ILocationManager
void registerLocationListener(String provider, in LocationRequest request, in ILocationListener listener, String packageName, String attributionTag, String listenerId);
void unregisterLocationListener(in ILocationListener listener);
- void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent intent, String packageName, String attributionTag);
+ void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent pendingIntent, String packageName, String attributionTag);
void unregisterLocationPendingIntent(in PendingIntent intent);
+ void injectLocation(in Location location);
+
void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag);
void removeGeofence(in PendingIntent intent);
@@ -89,7 +91,6 @@ interface ILocationManager
void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String attributionTag);
void flushGnssBatch();
void stopGnssBatch();
- void injectLocation(in Location location);
List<String> getAllProviders();
List<String> getProviders(in Criteria criteria, boolean enabledOnly);
diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java
index 2738ff4ff38c..0ff0a723237b 100644
--- a/location/java/android/location/LocationListener.java
+++ b/location/java/android/location/LocationListener.java
@@ -19,12 +19,11 @@ package android.location;
import android.annotation.NonNull;
import android.os.Bundle;
+import java.util.concurrent.Executor;
+
/**
- * Used for receiving notifications from the LocationManager when
- * the location has changed. These methods are called if the
- * LocationListener has been registered with the location manager service
- * using the {@link LocationManager#requestLocationUpdates(String, long, float, LocationListener)}
- * method.
+ * Used for receiving notifications when the device location has changed. These methods are called
+ * when the listener has been registered with the LocationManager.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
@@ -32,6 +31,8 @@ import android.os.Bundle;
* <a href="{@docRoot}guide/topics/location/obtaining-user-location.html">Obtaining User
* Location</a> developer guide.</p>
* </div>
+ *
+ * @see LocationManager#requestLocationUpdates(String, LocationRequest, Executor, LocationListener)
*/
public interface LocationListener {
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index ac775ca05cab..3493693ac67e 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -216,15 +216,15 @@ public class LocationManager {
* Key used for an extra holding a boolean enabled/disabled status value when a provider
* enabled/disabled event is broadcast using a PendingIntent.
*
- * @see #requestLocationUpdates(String, long, float, PendingIntent)
+ * @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
*/
public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
/**
- * Key used for an extra holding a {@link Location} value when a location change is broadcast
- * using a PendingIntent.
+ * Key used for an extra holding a {@link Location} value when a location change is sent using
+ * a PendingIntent.
*
- * @see #requestLocationUpdates(String, long, float, PendingIntent)
+ * @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
*/
public static final String KEY_LOCATION_CHANGED = "location";
@@ -1322,27 +1322,26 @@ public class LocationManager {
Preconditions.checkArgument(provider != null, "invalid null provider");
Preconditions.checkArgument(locationRequest != null, "invalid null location request");
- synchronized (sLocationListeners) {
- WeakReference<LocationListenerTransport> reference = sLocationListeners.get(listener);
- LocationListenerTransport transport = reference != null ? reference.get() : null;
- if (transport == null) {
- transport = new LocationListenerTransport(listener, executor);
- sLocationListeners.put(listener, new WeakReference<>(transport));
- } else {
- transport.setExecutor(executor);
- }
+ try {
+ synchronized (sLocationListeners) {
+ WeakReference<LocationListenerTransport> reference = sLocationListeners.get(
+ listener);
+ LocationListenerTransport transport = reference != null ? reference.get() : null;
+ if (transport == null) {
+ transport = new LocationListenerTransport(listener, executor);
+ } else {
+ Preconditions.checkState(transport.isRegistered());
+ transport.setExecutor(executor);
+ }
- try {
- // making the service call while under lock is less than ideal since LMS must
- // make sure that callbacks are not made on the same thread - however it is the
- // easiest way to guarantee that clients will not receive callbacks after
- // unregistration is complete.
mService.registerLocationListener(provider, locationRequest, transport,
mContext.getPackageName(), mContext.getAttributionTag(),
AppOpsManager.toReceiverId(listener));
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+
+ sLocationListeners.put(listener, new WeakReference<>(transport));
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -1429,23 +1428,17 @@ public class LocationManager {
public void removeUpdates(@NonNull LocationListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener");
- synchronized (sLocationListeners) {
- WeakReference<LocationListenerTransport> reference = sLocationListeners.remove(
- listener);
- LocationListenerTransport transport = reference != null ? reference.get() : null;
- if (transport != null) {
- transport.unregister();
-
- try {
- // making the service call while under lock is less than ideal since LMS must
- // make sure that callbacks are not made on the same thread - however it is the
- // easiest way to guarantee that clients will not receive callbacks after
- // unregistration is complete.
+ try {
+ synchronized (sLocationListeners) {
+ WeakReference<LocationListenerTransport> ref = sLocationListeners.remove(listener);
+ LocationListenerTransport transport = ref != null ? ref.get() : null;
+ if (transport != null) {
+ transport.unregister();
mService.unregisterLocationListener(transport);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
}
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -2568,7 +2561,7 @@ public class LocationManager {
@Nullable private volatile LocationListener mListener;
LocationListenerTransport(LocationListener listener, Executor executor) {
- Preconditions.checkArgument(listener != null, "invalid null listener/callback");
+ Preconditions.checkArgument(listener != null, "invalid null listener");
mListener = listener;
setExecutor(executor);
}
@@ -2578,6 +2571,10 @@ public class LocationManager {
mExecutor = executor;
}
+ boolean isRegistered() {
+ return mListener != null;
+ }
+
void unregister() {
mListener = null;
}
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7a3a4b23d532..32ac374b33c2 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -171,7 +171,9 @@ public abstract class LocationProviderBase {
if (manager != null) {
try {
manager.onSetAllowed(mAllowed);
- } catch (RemoteException | RuntimeException e) {
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@@ -191,7 +193,9 @@ public abstract class LocationProviderBase {
if (manager != null) {
try {
manager.onSetProperties(mProperties);
- } catch (RemoteException | RuntimeException e) {
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@@ -248,7 +252,9 @@ public abstract class LocationProviderBase {
try {
manager.onReportLocation(location);
- } catch (RemoteException | RuntimeException e) {
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@@ -339,6 +345,8 @@ public abstract class LocationProviderBase {
manager.onSetProperties(mProperties);
manager.onSetAllowed(mAllowed);
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (RuntimeException e) {
Log.w(mTag, e);
}
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index a98ea695eaf1..8adedf5779d1 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -97,6 +97,7 @@ package android {
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
+ field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
@@ -17846,6 +17847,7 @@ package android.hardware.camera2 {
public class CaptureResult extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureResult.Key<?>> {
method @Nullable public <T> T get(android.hardware.camera2.CaptureResult.Key<T>);
+ method @NonNull public String getCameraId();
method public long getFrameNumber();
method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getKeys();
method @NonNull public android.hardware.camera2.CaptureRequest getRequest();
@@ -37407,6 +37409,9 @@ package android.provider {
ctor public CallLog.Calls();
method public static String getLastOutgoingCall(android.content.Context);
field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
+ field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L
+ field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L
+ field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final String BLOCK_REASON = "block_reason";
field public static final int BLOCK_REASON_BLOCKED_NUMBER = 3; // 0x3
@@ -37452,6 +37457,8 @@ package android.provider {
field public static final String IS_READ = "is_read";
field public static final String LAST_MODIFIED = "last_modified";
field public static final String LIMIT_PARAM_KEY = "limit";
+ field public static final String MISSED_REASON = "missed_reason";
+ field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L
field public static final int MISSED_TYPE = 3; // 0x3
field public static final String NEW = "new";
field public static final String NUMBER = "number";
@@ -37468,6 +37475,13 @@ package android.provider {
field public static final int REJECTED_TYPE = 5; // 0x5
field public static final String TRANSCRIPTION = "transcription";
field public static final String TYPE = "type";
+ field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L
+ field public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 2097152L; // 0x200000L
+ field public static final long USER_MISSED_DND_MODE = 262144L; // 0x40000L
+ field public static final long USER_MISSED_LOW_RING_VOLUME = 524288L; // 0x80000L
+ field public static final long USER_MISSED_NO_ANSWER = 65536L; // 0x10000L
+ field public static final long USER_MISSED_NO_VIBRATE = 1048576L; // 0x100000L
+ field public static final long USER_MISSED_SHORT_RING = 131072L; // 0x20000L
field public static final String VIA_NUMBER = "via_number";
field public static final int VOICEMAIL_TYPE = 4; // 0x4
field public static final String VOICEMAIL_URI = "voicemail_uri";
@@ -41976,6 +41990,7 @@ package android.service.controls.templates {
field public static final int TYPE_RANGE = 2; // 0x2
field public static final int TYPE_STATELESS = 8; // 0x8
field public static final int TYPE_TEMPERATURE = 7; // 0x7
+ field public static final int TYPE_THUMBNAIL = 3; // 0x3
field public static final int TYPE_TOGGLE = 1; // 0x1
field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6
}
@@ -42015,6 +42030,14 @@ package android.service.controls.templates {
field public static final int MODE_UNKNOWN = 0; // 0x0
}
+ public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate {
+ ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence);
+ method @NonNull public CharSequence getContentDescription();
+ method public int getTemplateType();
+ method @NonNull public android.graphics.drawable.Icon getThumbnail();
+ method public boolean isActive();
+ }
+
public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate {
ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate);
ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate);
@@ -46009,6 +46032,7 @@ package android.telephony {
method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
+ method public int getNrState();
method @Nullable public String getRegisteredPlmn();
method public int getTransportType();
method public boolean isRegistered();
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 79c82695e911..fe40892a959b 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -403,6 +403,7 @@ package android.app {
field public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats";
field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
+ field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
field public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
@@ -865,7 +866,6 @@ package android.app.admin {
field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2
field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0
field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
- field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
@@ -10659,6 +10659,7 @@ package android.telephony {
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
+ method public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -10692,6 +10693,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+ method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
@@ -10731,6 +10733,11 @@ package android.telephony {
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -10763,6 +10770,9 @@ package android.telephony {
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
+ field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
diff --git a/packages/CarSystemUI/res/values-af/strings.xml b/packages/CarSystemUI/res/values-af/strings.xml
index b377ec47f6c6..cf288d74d61c 100644
--- a/packages/CarSystemUI/res/values-af/strings.xml
+++ b/packages/CarSystemUI/res/values-af/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Enige gebruiker kan programme vir al die ander gebruikers opdateer."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laai tans"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Laai tans gebruiker (van <xliff:g id="FROM_USER">%1$d</xliff:g> na <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Maak toe"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-am/strings.xml b/packages/CarSystemUI/res/values-am/strings.xml
index 4f2bba8aa1de..8281631312b7 100644
--- a/packages/CarSystemUI/res/values-am/strings.xml
+++ b/packages/CarSystemUI/res/values-am/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ማንኛውም ተጠቃሚ መተግበሪያዎችን ለሌሎች ተጠቃሚዎች ሁሉ ማዘመን ይችላል።"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"በመጫን ላይ"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ተጠቃሚን (ከ<xliff:g id="FROM_USER">%1$d</xliff:g> ወደ <xliff:g id="TO_USER">%2$d</xliff:g>) በመጫን ላይ"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ዝጋ"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-as/strings.xml b/packages/CarSystemUI/res/values-as/strings.xml
index edc36215bcce..d8710555149b 100644
--- a/packages/CarSystemUI/res/values-as/strings.xml
+++ b/packages/CarSystemUI/res/values-as/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"যিকোনো ব্যৱহাৰকাৰীয়ে অন্য ব্যৱহাৰকাৰীৰ বাবে এপ্‌সমূহ আপডে’ট কৰিব পাৰে।"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ল’ড হৈ আছে"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ব্যৱহাৰকাৰী ল’ড হৈ আছে (<xliff:g id="FROM_USER">%1$d</xliff:g>ৰ পৰা to <xliff:g id="TO_USER">%2$d</xliff:g>লৈ)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"বন্ধ কৰক"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-az/strings.xml b/packages/CarSystemUI/res/values-az/strings.xml
index 398f5c38c03e..89c9eb4ee258 100644
--- a/packages/CarSystemUI/res/values-az/strings.xml
+++ b/packages/CarSystemUI/res/values-az/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"İstənilən istifadəçi digər bütün istifadəçilər üçün tətbiqləri güncəlləyə bilər."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Yüklənir"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"İstifadəçi yüklənir (<xliff:g id="FROM_USER">%1$d</xliff:g>-<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Qapadın"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml b/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml
index 6c1979f7a95a..6aee01321ade 100644
--- a/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Svaki korisnik može da ažurira aplikacije za sve ostale korisnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Učitava se"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Profil korisnika se učitava (iz<xliff:g id="FROM_USER">%1$d</xliff:g> u <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-be/strings.xml b/packages/CarSystemUI/res/values-be/strings.xml
index 4e9794855e47..fde42732283f 100644
--- a/packages/CarSystemUI/res/values-be/strings.xml
+++ b/packages/CarSystemUI/res/values-be/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Кожны карыстальнік прылады можа абнаўляць праграмы для ўсіх іншых карыстальнікаў."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ідзе загрузка"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ідзе загрузка профілю карыстальніка (ад <xliff:g id="FROM_USER">%1$d</xliff:g> да <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрыць"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-bg/strings.xml b/packages/CarSystemUI/res/values-bg/strings.xml
index 7dfab54b8add..25f284515b9f 100644
--- a/packages/CarSystemUI/res/values-bg/strings.xml
+++ b/packages/CarSystemUI/res/values-bg/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Всеки потребител може да актуализира приложенията за всички останали потребители."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Зарежда се"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Потребителят се зарежда (от <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затваряне"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-bs/strings.xml b/packages/CarSystemUI/res/values-bs/strings.xml
index 119f2d7bf793..588771e41740 100644
--- a/packages/CarSystemUI/res/values-bs/strings.xml
+++ b/packages/CarSystemUI/res/values-bs/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bilo koji korisnik može ažurirati aplikacije za sve druge korisnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Učitavanje"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Učitavanje korisnika (od korisnika <xliff:g id="FROM_USER">%1$d</xliff:g> do korisnika <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ca/strings.xml b/packages/CarSystemUI/res/values-ca/strings.xml
index b1e722e95c58..cbd469b68a62 100644
--- a/packages/CarSystemUI/res/values-ca/strings.xml
+++ b/packages/CarSystemUI/res/values-ca/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualsevol usuari pot actualitzar les aplicacions de la resta d\'usuaris."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"S\'està carregant"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"S\'està carregant l\'usuari (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tanca"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-cs/strings.xml b/packages/CarSystemUI/res/values-cs/strings.xml
index dd4472f06aa3..7657e32b0223 100644
--- a/packages/CarSystemUI/res/values-cs/strings.xml
+++ b/packages/CarSystemUI/res/values-cs/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Každý uživatel může aktualizovat aplikace všech ostatních uživatelů."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Načítání"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Načítání uživatele (předchozí: <xliff:g id="FROM_USER">%1$d</xliff:g>, následující: <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zavřít"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-da/strings.xml b/packages/CarSystemUI/res/values-da/strings.xml
index 6c08aa56bc7e..120929e34347 100644
--- a/packages/CarSystemUI/res/values-da/strings.xml
+++ b/packages/CarSystemUI/res/values-da/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Alle brugere kan opdatere apps for alle andre brugere."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Indlæser"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Indlæser bruger (fra <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Luk"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-el/strings.xml b/packages/CarSystemUI/res/values-el/strings.xml
index 66f8d18472c9..9b24fa488923 100644
--- a/packages/CarSystemUI/res/values-el/strings.xml
+++ b/packages/CarSystemUI/res/values-el/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Οποιοσδήποτε χρήστης μπορεί να ενημερώσει τις εφαρμογές για όλους τους άλλους χρήστες."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Φόρτωση"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Φόρτωση χρήστη (από <xliff:g id="FROM_USER">%1$d</xliff:g> έως <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Κλείσιμο"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rAU/strings.xml b/packages/CarSystemUI/res/values-en-rAU/strings.xml
index b3e358fbb6ef..8eb76c20d8a2 100644
--- a/packages/CarSystemUI/res/values-en-rAU/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rAU/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rCA/strings.xml b/packages/CarSystemUI/res/values-en-rCA/strings.xml
index b3e358fbb6ef..8eb76c20d8a2 100644
--- a/packages/CarSystemUI/res/values-en-rCA/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rCA/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rGB/strings.xml b/packages/CarSystemUI/res/values-en-rGB/strings.xml
index b3e358fbb6ef..8eb76c20d8a2 100644
--- a/packages/CarSystemUI/res/values-en-rGB/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rGB/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rIN/strings.xml b/packages/CarSystemUI/res/values-en-rIN/strings.xml
index b3e358fbb6ef..8eb76c20d8a2 100644
--- a/packages/CarSystemUI/res/values-en-rIN/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rIN/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-en-rXC/strings.xml b/packages/CarSystemUI/res/values-en-rXC/strings.xml
index eaf6f51d3092..37a568bf3e5a 100644
--- a/packages/CarSystemUI/res/values-en-rXC/strings.xml
+++ b/packages/CarSystemUI/res/values-en-rXC/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎Any user can update apps for all other users.‎‏‎‎‏‎"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‏‎‎Loading‎‏‎‎‏‎"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎Loading user (from ‎‏‎‎‏‏‎<xliff:g id="FROM_USER">%1$d</xliff:g>‎‏‎‎‏‏‏‎ to ‎‏‎‎‏‏‎<xliff:g id="TO_USER">%2$d</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎Close‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-es-rUS/strings.xml b/packages/CarSystemUI/res/values-es-rUS/strings.xml
index 6a5f8ce43318..16aba86f3c3f 100644
--- a/packages/CarSystemUI/res/values-es-rUS/strings.xml
+++ b/packages/CarSystemUI/res/values-es-rUS/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Cualquier usuario podrá actualizar las apps de otras personas."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Cargando"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Cargando usuario (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Cerrar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-es/strings.xml b/packages/CarSystemUI/res/values-es/strings.xml
index c43d7e54d559..8aad2cad9cf2 100644
--- a/packages/CarSystemUI/res/values-es/strings.xml
+++ b/packages/CarSystemUI/res/values-es/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Cualquier usuario puede actualizar las aplicaciones del resto de los usuarios."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Cargando"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Cargando usuario (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Cerrar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-et/strings.xml b/packages/CarSystemUI/res/values-et/strings.xml
index ad82d5fc230b..14ec9df45c2d 100644
--- a/packages/CarSystemUI/res/values-et/strings.xml
+++ b/packages/CarSystemUI/res/values-et/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Iga kasutaja saab rakendusi värskendada kõigi teiste kasutajate jaoks."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laadimine"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Kasutaja laadimine (<xliff:g id="FROM_USER">%1$d</xliff:g> &gt; <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sule"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fa/strings.xml b/packages/CarSystemUI/res/values-fa/strings.xml
index ef37b654bde9..3f53b1145b96 100644
--- a/packages/CarSystemUI/res/values-fa/strings.xml
+++ b/packages/CarSystemUI/res/values-fa/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"هر کاربری می‌تواند برنامه‌ها را برای همه کاربران دیگر به‌روزرسانی کند."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"درحال بارگیری"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"بارگیری کاربر (از <xliff:g id="FROM_USER">%1$d</xliff:g> تا <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"بستن"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fi/strings.xml b/packages/CarSystemUI/res/values-fi/strings.xml
index 10bb0c5b782c..79b53f6daa2c 100644
--- a/packages/CarSystemUI/res/values-fi/strings.xml
+++ b/packages/CarSystemUI/res/values-fi/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Kaikki käyttäjät voivat päivittää muiden käyttäjien sovelluksia."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ladataan"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ladataan käyttäjäprofiilia (<xliff:g id="FROM_USER">%1$d</xliff:g>–<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sulje"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fr-rCA/strings.xml b/packages/CarSystemUI/res/values-fr-rCA/strings.xml
index bebd3f441410..b1905490f229 100644
--- a/packages/CarSystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/CarSystemUI/res/values-fr-rCA/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Tout utilisateur peut mettre à jour les applications pour tous les autres utilisateurs."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Chargement en cours…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Chargement de l\'utilisateur (de <xliff:g id="FROM_USER">%1$d</xliff:g> vers <xliff:g id="TO_USER">%2$d</xliff:g>) en cours…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fermer"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-fr/strings.xml b/packages/CarSystemUI/res/values-fr/strings.xml
index 3d498d2f9ca7..5a905a000c42 100644
--- a/packages/CarSystemUI/res/values-fr/strings.xml
+++ b/packages/CarSystemUI/res/values-fr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"N\'importe quel utilisateur peut mettre à jour les applications pour tous les autres utilisateurs."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Chargement…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Chargement de l\'utilisateur (de <xliff:g id="FROM_USER">%1$d</xliff:g> à <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fermer"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hi/strings.xml b/packages/CarSystemUI/res/values-hi/strings.xml
index 95454a53709f..83321fd630b9 100644
--- a/packages/CarSystemUI/res/values-hi/strings.xml
+++ b/packages/CarSystemUI/res/values-hi/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"कोई भी उपयोगकर्ता, बाकी सभी उपयोगकर्ताओं के लिए ऐप्लिकेशन अपडेट कर सकता है."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"लोड हो रही है"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"उपयोगकर्ता को लोड किया जा रहा है (<xliff:g id="FROM_USER">%1$d</xliff:g> से <xliff:g id="TO_USER">%2$d</xliff:g> पर)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"बंद करें"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hr/strings.xml b/packages/CarSystemUI/res/values-hr/strings.xml
index f3aaf63eac18..872fc69d8cfb 100644
--- a/packages/CarSystemUI/res/values-hr/strings.xml
+++ b/packages/CarSystemUI/res/values-hr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Svaki korisnik može ažurirati aplikacije za ostale korisnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Učitavanje"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Učitavanje korisnika (od <xliff:g id="FROM_USER">%1$d</xliff:g> do <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hu/strings.xml b/packages/CarSystemUI/res/values-hu/strings.xml
index b63ba8b8ed60..63328f370ed3 100644
--- a/packages/CarSystemUI/res/values-hu/strings.xml
+++ b/packages/CarSystemUI/res/values-hu/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bármely felhasználó frissítheti az alkalmazásokat az összes felhasználó számára."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Betöltés"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Felhasználó betöltése (<xliff:g id="FROM_USER">%1$d</xliff:g> → <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Bezárás"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-hy/strings.xml b/packages/CarSystemUI/res/values-hy/strings.xml
index e2a2c6bf459c..778f6954da3d 100644
--- a/packages/CarSystemUI/res/values-hy/strings.xml
+++ b/packages/CarSystemUI/res/values-hy/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Ցանկացած օգտատեր կարող է թարմացնել հավելվածները բոլոր մյուս հաշիվների համար։"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Բեռնում"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Օգտատերը բեռնվում է (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Փակել"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-in/strings.xml b/packages/CarSystemUI/res/values-in/strings.xml
index 0a70d261b77d..386d79e0e88b 100644
--- a/packages/CarSystemUI/res/values-in/strings.xml
+++ b/packages/CarSystemUI/res/values-in/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Setiap pengguna dapat mengupdate aplikasi untuk semua pengguna lain."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Memuat"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Memuat pengguna (dari <xliff:g id="FROM_USER">%1$d</xliff:g> menjadi <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tutup"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-is/strings.xml b/packages/CarSystemUI/res/values-is/strings.xml
index ea6b031c72b9..5a927aa0928a 100644
--- a/packages/CarSystemUI/res/values-is/strings.xml
+++ b/packages/CarSystemUI/res/values-is/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Allir notendur geta uppfært forrit fyrir alla aðra notendur."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Hleður"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Hleður notanda (frá <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Loka"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-it/strings.xml b/packages/CarSystemUI/res/values-it/strings.xml
index ecbcd5d9dadd..41d7ad4f70ee 100644
--- a/packages/CarSystemUI/res/values-it/strings.xml
+++ b/packages/CarSystemUI/res/values-it/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualsiasi utente può aggiornare le app per tutti gli altri."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Caricamento"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Caricamento dell\'utente (da <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Chiudi"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-iw/strings.xml b/packages/CarSystemUI/res/values-iw/strings.xml
index fe182a3b7251..f419cac56d48 100644
--- a/packages/CarSystemUI/res/values-iw/strings.xml
+++ b/packages/CarSystemUI/res/values-iw/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"כל משתמש יכול לעדכן אפליקציות לכל שאר המשתמשים."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"בטעינה"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"המשתמש בטעינה (מהמשתמש <xliff:g id="FROM_USER">%1$d</xliff:g> אל <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"סגירה"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ja/strings.xml b/packages/CarSystemUI/res/values-ja/strings.xml
index 14486758dcd1..9cf056fdf7c6 100644
--- a/packages/CarSystemUI/res/values-ja/strings.xml
+++ b/packages/CarSystemUI/res/values-ja/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"どのユーザーも他のすべてのユーザーに代わってアプリを更新できます。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"読み込んでいます"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ユーザーを読み込んでいます(<xliff:g id="FROM_USER">%1$d</xliff:g>~<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"閉じる"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ka/strings.xml b/packages/CarSystemUI/res/values-ka/strings.xml
index 0fef7e55f27e..7d62c62a8abe 100644
--- a/packages/CarSystemUI/res/values-ka/strings.xml
+++ b/packages/CarSystemUI/res/values-ka/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ნებისმიერ მომხმარებელს შეუძლია აპები ყველა სხვა მომხმარებლისათვის განაახლოს."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"იტვირთება"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"იტვირთება მომხმარებელი (<xliff:g id="FROM_USER">%1$d</xliff:g>-დან <xliff:g id="TO_USER">%2$d</xliff:g>-მდე)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"დახურვა"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-kk/strings.xml b/packages/CarSystemUI/res/values-kk/strings.xml
index a4cf78709515..2dd1b66f1778 100644
--- a/packages/CarSystemUI/res/values-kk/strings.xml
+++ b/packages/CarSystemUI/res/values-kk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Кез келген пайдаланушы қолданбаларды басқа пайдаланушылар үшін жаңарта алады."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Жүктелуде"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Пайдаланушы профилі жүктелуде (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Жабу"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-km/strings.xml b/packages/CarSystemUI/res/values-km/strings.xml
index 7b9a093d7f39..709cfe572ce9 100644
--- a/packages/CarSystemUI/res/values-km/strings.xml
+++ b/packages/CarSystemUI/res/values-km/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"អ្នកប្រើប្រាស់​ណាក៏​អាច​ដំឡើងកំណែ​កម្មវិធី​សម្រាប់​អ្នកប្រើប្រាស់ទាំងអស់​ផ្សេងទៀត​បានដែរ។"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"កំពុងផ្ទុក"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"កំពុងផ្ទុក​អ្នកប្រើប្រាស់ (ពី <xliff:g id="FROM_USER">%1$d</xliff:g> ដល់ <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"បិទ"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ko/strings.xml b/packages/CarSystemUI/res/values-ko/strings.xml
index b570c5c6b81e..17a24667b547 100644
--- a/packages/CarSystemUI/res/values-ko/strings.xml
+++ b/packages/CarSystemUI/res/values-ko/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"누구나 다른 모든 사용자를 위해 앱을 업데이트할 수 있습니다."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"로드 중"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"사용자 로드 중(<xliff:g id="FROM_USER">%1$d</xliff:g>님에서 <xliff:g id="TO_USER">%2$d</xliff:g>님으로)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"닫기"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ky/strings.xml b/packages/CarSystemUI/res/values-ky/strings.xml
index c66b34ffb9c1..dd9225a1aa0b 100644
--- a/packages/CarSystemUI/res/values-ky/strings.xml
+++ b/packages/CarSystemUI/res/values-ky/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Колдонмолорду бир колдонуучу калган бардык колдонуучулар үчүн да жаңырта алат."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Жүктөлүүдө"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Колдонуучу тууралуу маалымат жүктөлүүдө (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Жабуу"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-lo/strings.xml b/packages/CarSystemUI/res/values-lo/strings.xml
index 2bf19e03b82a..bc94a5104c6b 100644
--- a/packages/CarSystemUI/res/values-lo/strings.xml
+++ b/packages/CarSystemUI/res/values-lo/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ຜູ້ໃຊ້ຕ່າງໆສາມາດອັບເດດແອັບສຳລັບຜູ້ໃຊ້ອື່ນທັງໝົດໄດ້."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ກຳລັງໂຫຼດ"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ກຳລັງໂຫຼດຜູ້ໃຊ້ (ຈາກ <xliff:g id="FROM_USER">%1$d</xliff:g> ໄປຍັງ <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ປິດ"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-lt/strings.xml b/packages/CarSystemUI/res/values-lt/strings.xml
index 1cae1e907193..a47ad5909f12 100644
--- a/packages/CarSystemUI/res/values-lt/strings.xml
+++ b/packages/CarSystemUI/res/values-lt/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bet kuris naudotojas gali atnaujinti visų kitų naudotojų programas."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Įkeliama"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Įkeliamas naudotojo profilis (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Uždaryti"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-lv/strings.xml b/packages/CarSystemUI/res/values-lv/strings.xml
index 62b8bf8d98eb..cb7c8b9f1575 100644
--- a/packages/CarSystemUI/res/values-lv/strings.xml
+++ b/packages/CarSystemUI/res/values-lv/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Ikviens lietotājs var atjaunināt lietotnes visu lietotāju vārdā."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Notiek ielāde…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Notiek lietotāja profila ielāde (<xliff:g id="FROM_USER">%1$d</xliff:g>–<xliff:g id="TO_USER">%2$d</xliff:g>)…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Aizvērt"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-mk/strings.xml b/packages/CarSystemUI/res/values-mk/strings.xml
index 3e7ad63ae2d9..cd2ae973a078 100644
--- a/packages/CarSystemUI/res/values-mk/strings.xml
+++ b/packages/CarSystemUI/res/values-mk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Секој корисник може да ажурира апликации за сите други корисници."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Се вчитува"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Се вчитува корисникот (од <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затвори"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-mn/strings.xml b/packages/CarSystemUI/res/values-mn/strings.xml
index 45921d26172e..33bcd275117d 100644
--- a/packages/CarSystemUI/res/values-mn/strings.xml
+++ b/packages/CarSystemUI/res/values-mn/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Бусад бүх хэрэглэгчийн аппыг дурын хэрэглэгч шинэчлэх боломжтой."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ачаалж байна"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Хэрэглэгчийг ачаалж байна (<xliff:g id="FROM_USER">%1$d</xliff:g>-с <xliff:g id="TO_USER">%2$d</xliff:g> хүртэл)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Хаах"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ms/strings.xml b/packages/CarSystemUI/res/values-ms/strings.xml
index 1a43d9c7cdef..0bb683b0a58d 100644
--- a/packages/CarSystemUI/res/values-ms/strings.xml
+++ b/packages/CarSystemUI/res/values-ms/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Mana-mana pengguna boleh mengemas kini apl untuk semua pengguna lain."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Memuatkan"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Memuatkan pengguna (daripada <xliff:g id="FROM_USER">%1$d</xliff:g> hingga <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tutup"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-my/strings.xml b/packages/CarSystemUI/res/values-my/strings.xml
index 4f3922b373b5..4e7ca39a2b12 100644
--- a/packages/CarSystemUI/res/values-my/strings.xml
+++ b/packages/CarSystemUI/res/values-my/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"မည်သူမဆို အသုံးပြုသူအားလုံးအတွက် အက်ပ်များကို အပ်ဒိတ်လုပ်နိုင်သည်။"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ဖွင့်နေသည်"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"အသုံးပြုသူကို ဖွင့်နေသည် (<xliff:g id="FROM_USER">%1$d</xliff:g> မှ <xliff:g id="TO_USER">%2$d</xliff:g> သို့)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ပိတ်ရန်"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-nb/strings.xml b/packages/CarSystemUI/res/values-nb/strings.xml
index 5b6166feba0f..0b2856f0d7b2 100644
--- a/packages/CarSystemUI/res/values-nb/strings.xml
+++ b/packages/CarSystemUI/res/values-nb/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Alle brukere kan oppdatere apper for alle andre brukere."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laster inn"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Laster inn brukeren (fra <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Lukk"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-nl/strings.xml b/packages/CarSystemUI/res/values-nl/strings.xml
index d79f2b1d10f4..4765f71b8b61 100644
--- a/packages/CarSystemUI/res/values-nl/strings.xml
+++ b/packages/CarSystemUI/res/values-nl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Elke gebruiker kan apps updaten voor alle andere gebruikers"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Laden"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Gebruiker laden (van <xliff:g id="FROM_USER">%1$d</xliff:g> naar <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sluiten"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-pl/strings.xml b/packages/CarSystemUI/res/values-pl/strings.xml
index dd8c1892b63e..52b90f1b5b96 100644
--- a/packages/CarSystemUI/res/values-pl/strings.xml
+++ b/packages/CarSystemUI/res/values-pl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Każdy użytkownik może aktualizować aplikacje wszystkich innych użytkowników."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Ładuję"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ładuję użytkownika (od <xliff:g id="FROM_USER">%1$d</xliff:g> do <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zamknij"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-pt-rPT/strings.xml b/packages/CarSystemUI/res/values-pt-rPT/strings.xml
index c7f5ecf00707..2dffa17e8f1c 100644
--- a/packages/CarSystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/CarSystemUI/res/values-pt-rPT/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualquer utilizador pode atualizar apps para todos os outros utilizadores."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"A carregar…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"A carregar o utilizador (de <xliff:g id="FROM_USER">%1$d</xliff:g> para <xliff:g id="TO_USER">%2$d</xliff:g>)…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fechar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-pt/strings.xml b/packages/CarSystemUI/res/values-pt/strings.xml
index 0712fb82f7fd..a7c44d2522d6 100644
--- a/packages/CarSystemUI/res/values-pt/strings.xml
+++ b/packages/CarSystemUI/res/values-pt/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qualquer usuário pode atualizar apps para os demais usuários."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Carregando"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Carregando usuário (de <xliff:g id="FROM_USER">%1$d</xliff:g> para <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fechar"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ro/strings.xml b/packages/CarSystemUI/res/values-ro/strings.xml
index 60fd4fef41c0..1a4e71d9eea8 100644
--- a/packages/CarSystemUI/res/values-ro/strings.xml
+++ b/packages/CarSystemUI/res/values-ro/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Orice utilizator poate actualiza aplicațiile pentru toți ceilalți utilizatori."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Se încarcă"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Se încarcă utilizatorul (de la <xliff:g id="FROM_USER">%1$d</xliff:g> la <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Închideți"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ru/strings.xml b/packages/CarSystemUI/res/values-ru/strings.xml
index a043d24789a9..330ba2f0bdbd 100644
--- a/packages/CarSystemUI/res/values-ru/strings.xml
+++ b/packages/CarSystemUI/res/values-ru/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Любой пользователь устройства может обновлять приложения для всех аккаунтов."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Загрузка…"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Загрузка профиля пользователя (с <xliff:g id="FROM_USER">%1$d</xliff:g> по <xliff:g id="TO_USER">%2$d</xliff:g>)…"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрыть"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-si/strings.xml b/packages/CarSystemUI/res/values-si/strings.xml
index e14f64a7bca8..6391d28e9568 100644
--- a/packages/CarSystemUI/res/values-si/strings.xml
+++ b/packages/CarSystemUI/res/values-si/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"සියලුම අනෙක් පරිශීලකයින් සඳහා ඕනෑම පරිශීලකයෙකුට යෙදුම් යාවත්කාලීන කළ හැක."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"පූරණය වෙමින්"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"පරිශීලකයා පූරණය වෙමින් (<xliff:g id="FROM_USER">%1$d</xliff:g> සිට <xliff:g id="TO_USER">%2$d</xliff:g> වෙත)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"වසන්න"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sk/strings.xml b/packages/CarSystemUI/res/values-sk/strings.xml
index 8f98983877d9..b100a5d4cf5d 100644
--- a/packages/CarSystemUI/res/values-sk/strings.xml
+++ b/packages/CarSystemUI/res/values-sk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Ktorýkoľvek používateľ môže aktualizovať aplikácie všetkých ostatných používateľov."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Načítava sa"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Načítava sa používateľ (predchádzajúci: <xliff:g id="FROM_USER">%1$d</xliff:g>, nasledujúci: <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zavrieť"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sl/strings.xml b/packages/CarSystemUI/res/values-sl/strings.xml
index 6b471845b657..b67002bec7d4 100644
--- a/packages/CarSystemUI/res/values-sl/strings.xml
+++ b/packages/CarSystemUI/res/values-sl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Vsak uporabnik lahko posodobi aplikacije za vse druge uporabnike."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Nalaganje"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Nalaganje uporabnika (od uporabnika <xliff:g id="FROM_USER">%1$d</xliff:g> do uporabnika <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zapri"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sq/strings.xml b/packages/CarSystemUI/res/values-sq/strings.xml
index 18ea40131817..d19e1583664b 100644
--- a/packages/CarSystemUI/res/values-sq/strings.xml
+++ b/packages/CarSystemUI/res/values-sq/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Çdo përdorues mund t\'i përditësojë aplikacionet për të gjithë përdoruesit e tjerë."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Po ngarkohet"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Përdoruesi po ngarkohet (nga <xliff:g id="FROM_USER">%1$d</xliff:g> te <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Mbyll"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sr/strings.xml b/packages/CarSystemUI/res/values-sr/strings.xml
index 878a1c1aa425..a5fb5b4f6ad6 100644
--- a/packages/CarSystemUI/res/values-sr/strings.xml
+++ b/packages/CarSystemUI/res/values-sr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Сваки корисник може да ажурира апликације за све остале кориснике."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Учитава се"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Профил корисника се учитава (из<xliff:g id="FROM_USER">%1$d</xliff:g> у <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затвори"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sv/strings.xml b/packages/CarSystemUI/res/values-sv/strings.xml
index 905dd445eeab..8a942d6af080 100644
--- a/packages/CarSystemUI/res/values-sv/strings.xml
+++ b/packages/CarSystemUI/res/values-sv/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Alla användare kan uppdatera appar för andra användare."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Läser in"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Läser in användare (från <xliff:g id="FROM_USER">%1$d</xliff:g> till <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Stäng"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-sw/strings.xml b/packages/CarSystemUI/res/values-sw/strings.xml
index 3cb6098a3f1b..be03373c48a1 100644
--- a/packages/CarSystemUI/res/values-sw/strings.xml
+++ b/packages/CarSystemUI/res/values-sw/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Mtumiaji yeyote anaweza kusasisha programu za watumiaji wengine."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Inapakia"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Inapakia wasifu wa mtumiaji (kutoka <xliff:g id="FROM_USER">%1$d</xliff:g> kuwa <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Funga"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-ta/strings.xml b/packages/CarSystemUI/res/values-ta/strings.xml
index de52edee1621..a82a2f856bd4 100644
--- a/packages/CarSystemUI/res/values-ta/strings.xml
+++ b/packages/CarSystemUI/res/values-ta/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"எந்தப் பயனரும் பிற பயனர்கள் சார்பாக ஆப்ஸைப் புதுப்பிக்க முடியும்."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"ஏற்றுகிறது"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"பயனர் தகவலை ஏற்றுகிறது (<xliff:g id="FROM_USER">%1$d</xliff:g>லிருந்து <xliff:g id="TO_USER">%2$d</xliff:g> வரை)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"மூடுக"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-th/strings.xml b/packages/CarSystemUI/res/values-th/strings.xml
index 8f3012b5b4a5..dacf605888f5 100644
--- a/packages/CarSystemUI/res/values-th/strings.xml
+++ b/packages/CarSystemUI/res/values-th/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"ผู้ใช้ทุกคนจะอัปเดตแอปให้แก่ผู้ใช้คนอื่นๆ ได้"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"กำลังโหลด"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"กำลังโหลดผู้ใช้ (จาก <xliff:g id="FROM_USER">%1$d</xliff:g> ถึง <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ปิด"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-tl/strings.xml b/packages/CarSystemUI/res/values-tl/strings.xml
index 5b0e3b3cf19c..89d8cd201e5f 100644
--- a/packages/CarSystemUI/res/values-tl/strings.xml
+++ b/packages/CarSystemUI/res/values-tl/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Puwedeng i-update ng sinumang user ang mga app para sa lahat ng iba pang user."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Naglo-load"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Nilo-load ang user (mula kay <xliff:g id="FROM_USER">%1$d</xliff:g> papunta kay <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Isara"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-tr/strings.xml b/packages/CarSystemUI/res/values-tr/strings.xml
index 81fa01c16058..36bf694f30ab 100644
--- a/packages/CarSystemUI/res/values-tr/strings.xml
+++ b/packages/CarSystemUI/res/values-tr/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Herhangi bir kullanıcı, diğer tüm kullanıcılar için uygulamaları güncelleyebilir."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Yükleniyor"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Kullanıcı yükleniyor (<xliff:g id="FROM_USER">%1$d</xliff:g> kullanıcısından <xliff:g id="TO_USER">%2$d</xliff:g> kullanıcısına)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Kapat"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-uk/strings.xml b/packages/CarSystemUI/res/values-uk/strings.xml
index b7031c698815..391513f1b57a 100644
--- a/packages/CarSystemUI/res/values-uk/strings.xml
+++ b/packages/CarSystemUI/res/values-uk/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Усі користувачі можуть оновлювати додатки для решти людей."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Завантаження"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Завантаження профілю користувача (від <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрити"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-uz/strings.xml b/packages/CarSystemUI/res/values-uz/strings.xml
index 471e4591265a..398d1f5ce29e 100644
--- a/packages/CarSystemUI/res/values-uz/strings.xml
+++ b/packages/CarSystemUI/res/values-uz/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Qurilmaning istalgan foydalanuvchisi ilovalarni barcha hisoblar uchun yangilashi mumkin."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Yuklanmoqda"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Foydalanuvchi profili yuklanmoqda (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Yopish"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-vi/strings.xml b/packages/CarSystemUI/res/values-vi/strings.xml
index 26bdddc750cd..f15320fd1dcd 100644
--- a/packages/CarSystemUI/res/values-vi/strings.xml
+++ b/packages/CarSystemUI/res/values-vi/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Bất kỳ người dùng nào cũng có thể cập nhật ứng dụng cho tất cả những người dùng khác."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Đang tải"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Đang tải hồ sơ người dùng (từ <xliff:g id="FROM_USER">%1$d</xliff:g> sang <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Đóng"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zh-rCN/strings.xml b/packages/CarSystemUI/res/values-zh-rCN/strings.xml
index e7ca871337fa..a91f48c8b378 100644
--- a/packages/CarSystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/CarSystemUI/res/values-zh-rCN/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"任何用户均可为所有其他用户更新应用。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"正在加载"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在加载用户(从 <xliff:g id="FROM_USER">%1$d</xliff:g> 到 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"关闭"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zh-rHK/strings.xml b/packages/CarSystemUI/res/values-zh-rHK/strings.xml
index 268243fea6f7..7aa611606274 100644
--- a/packages/CarSystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/CarSystemUI/res/values-zh-rHK/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"任何使用者都可以為所有其他使用者更新應用程式。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"正在載入"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在載入使用者 (由 <xliff:g id="FROM_USER">%1$d</xliff:g> 至 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"關閉"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zh-rTW/strings.xml b/packages/CarSystemUI/res/values-zh-rTW/strings.xml
index 9dc0f1a03d3a..c062463905d7 100644
--- a/packages/CarSystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/CarSystemUI/res/values-zh-rTW/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"任何使用者都能為所有其他使用者更新應用程式。"</string>
<string name="car_loading_profile" msgid="4507385037552574474">"載入中"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在載入使用者 (從 <xliff:g id="FROM_USER">%1$d</xliff:g> 到 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"關閉"</string>
</resources>
diff --git a/packages/CarSystemUI/res/values-zu/strings.xml b/packages/CarSystemUI/res/values-zu/strings.xml
index 8845ff71c1bb..2dd33d827324 100644
--- a/packages/CarSystemUI/res/values-zu/strings.xml
+++ b/packages/CarSystemUI/res/values-zu/strings.xml
@@ -28,6 +28,5 @@
<string name="user_add_user_message_update" msgid="7061671307004867811">"Noma yimuphi umsebenzisi angabuyekeza izinhlelo zokusebenza zabanye abasebenzisi."</string>
<string name="car_loading_profile" msgid="4507385037552574474">"Iyalayisha"</string>
<string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ilayisha umsebenzisi (kusuka ku-<xliff:g id="FROM_USER">%1$d</xliff:g> kuya ku-<xliff:g id="TO_USER">%2$d</xliff:g>)"</string>
- <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) -->
- <skip />
+ <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Vala"</string>
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c4b36fb1ab29..b9e30fb950c6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -175,8 +175,8 @@ public class SecureSettings {
Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
Settings.Secure.TAPS_APP_TO_EXIT,
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
- Settings.Secure.PANIC_GESTURE_ENABLED,
- Settings.Secure.PANIC_SOUND_ENABLED,
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED,
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 7f694ad5d375..721bf730a343 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -264,8 +264,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TAPS_APP_TO_EXIT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.PANIC_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 94de48595b0d..f1d7e223c933 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2035,11 +2035,11 @@ class SettingsProtoDumpUtil {
final long emergencyResponseToken = p.start(SecureSettingsProto.EMERGENCY_RESPONSE);
dumpSetting(s, p,
- Settings.Secure.PANIC_GESTURE_ENABLED,
- SecureSettingsProto.EmergencyResponse.PANIC_GESTURE_ENABLED);
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ SecureSettingsProto.EmergencyResponse.EMERGENCY_GESTURE_ENABLED);
dumpSetting(s, p,
- Settings.Secure.PANIC_SOUND_ENABLED,
- SecureSettingsProto.EmergencyResponse.PANIC_SOUND_ENABLED);
+ Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED,
+ SecureSettingsProto.EmergencyResponse.EMERGENCY_GESTURE_SOUND_ENABLED);
p.end(emergencyResponseToken);
dumpSetting(s, p,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 407104504132..ddd0dac0e9db 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -586,6 +586,8 @@
</intent-filter>
</activity>
+ <activity android:name=".people.widget.LaunchConversationActivity" />
+
<!-- People Space Widget -->
<receiver
android:name=".people.widget.PeopleSpaceWidgetProvider"
diff --git a/packages/SystemUI/res/layout/people_space_widget_item.xml b/packages/SystemUI/res/layout/people_space_widget_item.xml
index a40bfffaabd7..e4de6f91769c 100644
--- a/packages/SystemUI/res/layout/people_space_widget_item.xml
+++ b/packages/SystemUI/res/layout/people_space_widget_item.xml
@@ -15,12 +15,12 @@
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:background="@drawable/people_space_tile_view_card"
+ android:id="@+id/item"
android:orientation="vertical"
android:padding="6dp"
android:layout_marginBottom="6dp"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
new file mode 100644
index 000000000000..22ffd2819c48
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.pip;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.SurfaceControl;
+
+/**
+ * TODO(b/171721389): unify this class with
+ * {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}, for instance, there should be one
+ * source of truth on enabling/disabling and the actual value of corner radius.
+ */
+public class PipSurfaceTransactionHelper {
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+ private final RectF mTmpSourceRectF = new RectF();
+ private final Rect mTmpDestinationRect = new Rect();
+
+ public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ mTmpSourceRectF.set(sourceBounds);
+ mTmpDestinationRect.set(sourceBounds);
+ mTmpDestinationRect.inset(insets);
+ // Scale by the shortest edge and offset such that the top/left of the scaled inset
+ // source rect aligns with the top/left of the destination bounds
+ final float scale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ final float left = destinationBounds.left - insets.left * scale;
+ final float top = destinationBounds.top - insets.top * scale;
+ mTmpTransform.setScale(scale, scale);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setWindowCrop(leash, mTmpDestinationRect)
+ .setPosition(leash, left, top);
+ }
+
+ public void reset(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds) {
+ resetScale(tx, leash, destinationBounds);
+ resetCornerRadius(tx, leash);
+ crop(tx, leash, destinationBounds);
+ }
+
+ public void resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ }
+
+ public void resetCornerRadius(SurfaceControl.Transaction tx, SurfaceControl leash) {
+ tx.setCornerRadius(leash, 0);
+ }
+
+ public void crop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
index 326c2aa37175..7b9ebc0d4656 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
@@ -17,8 +17,11 @@
package com.android.systemui.shared.system;
import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
public class TaskInfoCompat {
@@ -34,6 +37,10 @@ public class TaskInfoCompat {
return info.configuration.windowConfiguration.getWindowingMode();
}
+ public static Rect getWindowConfigurationBounds(TaskInfo info) {
+ return info.configuration.windowConfiguration.getBounds();
+ }
+
public static boolean supportsSplitScreenMultiWindow(TaskInfo info) {
return info.supportsSplitScreenMultiWindow;
}
@@ -45,4 +52,16 @@ public class TaskInfoCompat {
public static ActivityManager.TaskDescription getTaskDescription(TaskInfo info) {
return info.taskDescription;
}
+
+ public static ActivityInfo getTopActivityInfo(TaskInfo info) {
+ return info.topActivityInfo;
+ }
+
+ public static boolean isAutoEnterPipEnabled(PictureInPictureParams params) {
+ return params.isAutoEnterEnabled();
+ }
+
+ public static Rect getPipSourceRectHint(PictureInPictureParams params) {
+ return params.getSourceRectHint();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index e3ee2a10821b..fff185b99a1e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,18 @@ public class LogModule {
return buffer;
}
+ /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
+ @Provides
+ @SysUISingleton
+ @PrivacyLog
+ public static LogBuffer providePrivacyLogBuffer(
+ LogcatEchoTracker bufferFilter,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
new file mode 100644
index 000000000000..e96e532f94bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for privacy indicator-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface PrivacyLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 2ddcc5aa77fe..1a9dd712bd0e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -69,16 +69,29 @@ public class PeopleSpaceUtils {
/** Converts {@code drawable} to a {@link Bitmap}. */
public static Bitmap convertDrawableToBitmap(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+
if (drawable instanceof BitmapDrawable) {
- return ((BitmapDrawable) drawable).getBitmap();
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (bitmapDrawable.getBitmap() != null) {
+ return bitmapDrawable.getBitmap();
+ }
+ }
+
+ Bitmap bitmap;
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ // Single color bitmap will be created of 1x1 pixel
+ } else {
+ bitmap = Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888
+ );
}
- // We use max below because the drawable might have no intrinsic width/height (e.g. if the
- // drawable is a solid color).
- Bitmap bitmap =
- Bitmap.createBitmap(
- Math.max(drawable.getIntrinsicWidth(), 1),
- Math.max(drawable.getIntrinsicHeight(), 1),
- Bitmap.Config.ARGB_8888);
+
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
new file mode 100644
index 000000000000..44f173bc5175
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.widget;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.systemui.people.PeopleSpaceUtils;
+
+/** Proxy activity to launch ShortcutInfo's conversation. */
+public class LaunchConversationActivity extends Activity {
+ private static final String TAG = "PeopleSpaceLaunchConv";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate called");
+
+ Intent intent = getIntent();
+ ShortcutInfo shortcutInfo = (ShortcutInfo) intent.getParcelableExtra(
+ PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO
+ );
+ if (shortcutInfo != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Launching conversation with shortcutInfo id " + shortcutInfo.getId());
+ }
+ try {
+ LauncherApps launcherApps =
+ getApplicationContext().getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(
+ shortcutInfo, null, null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception starting shortcut:" + e);
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo.");
+ }
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 85801f9e7206..aa98b61ff947 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,6 +16,7 @@
package com.android.systemui.people.widget;
+import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
@@ -31,6 +32,8 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
private static final String TAG = "PeopleSpaceWidgetPvd";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+ public static final String EXTRA_SHORTCUT_INFO = "extra_shortcut_info";
+
/** Called when widget updates. */
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
@@ -45,6 +48,19 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
views.setRemoteAdapter(R.id.widget_list_view, intent);
+ Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
+ activityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NO_HISTORY
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ appWidgetId,
+ activityIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
+ views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent);
+
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list_view);
appWidgetManager.updateAppWidget(appWidgetId, views);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
index 093925a2664a..c68c30632b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
@@ -130,6 +130,10 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R
mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0)
)
);
+
+ Intent fillInIntent = new Intent();
+ fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO, shortcutInfo);
+ personView.setOnClickFillInIntent(R.id.item, fillInIntent);
} catch (Exception e) {
Log.e(TAG, "Couldn't retrieve shortcut information", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 3da1363f2a56..7359e79b26f5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -19,20 +19,31 @@ import com.android.systemui.R
typealias Privacy = PrivacyType
-enum class PrivacyType(val nameId: Int, val iconId: Int) {
+enum class PrivacyType(val nameId: Int, val iconId: Int, val logName: String) {
// This is uses the icons used by the corresponding permission groups in the AndroidManifest
- TYPE_CAMERA(R.string.privacy_type_camera,
- com.android.internal.R.drawable.perm_group_camera),
- TYPE_MICROPHONE(R.string.privacy_type_microphone,
- com.android.internal.R.drawable.perm_group_microphone),
- TYPE_LOCATION(R.string.privacy_type_location,
- com.android.internal.R.drawable.perm_group_location);
+ TYPE_CAMERA(
+ R.string.privacy_type_camera,
+ com.android.internal.R.drawable.perm_group_camera,
+ "camera"
+ ),
+ TYPE_MICROPHONE(
+ R.string.privacy_type_microphone,
+ com.android.internal.R.drawable.perm_group_microphone,
+ "microphone"
+ ),
+ TYPE_LOCATION(
+ R.string.privacy_type_location,
+ com.android.internal.R.drawable.perm_group_location,
+ "location"
+ );
fun getName(context: Context) = context.resources.getString(nameId)
fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
}
-data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication)
+data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) {
+ fun toLog(): String = "(${privacyType.logName}, ${application.packageName}(${application.uid}))"
+}
data class PrivacyApplication(val packageName: String, val uid: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index dc5ba693a658..87ffbd465109 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -48,6 +49,7 @@ class PrivacyItemController @Inject constructor(
@Background private val bgExecutor: Executor,
private val deviceConfigProxy: DeviceConfigProxy,
private val userTracker: UserTracker,
+ private val logger: PrivacyLogger,
dumpManager: DumpManager
) : Dumpable {
@@ -158,6 +160,7 @@ class PrivacyItemController @Inject constructor(
}
val userId = UserHandle.getUserId(uid)
if (userId in currentUserIds) {
+ logger.logUpdatedItemFromAppOps(code, uid, packageName, active)
update(false)
}
}
@@ -194,6 +197,7 @@ class PrivacyItemController @Inject constructor(
bgExecutor.execute {
if (updateUsers) {
currentUserIds = userTracker.userProfiles.map { it.id }
+ logger.logCurrentProfilesChanged(currentUserIds)
}
updateListAndNotifyChanges.run()
}
@@ -260,6 +264,8 @@ class PrivacyItemController @Inject constructor(
}
val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
.mapNotNull { toPrivacyItem(it) }.distinct()
+ logger.logUpdatedPrivacyItemsList(
+ list.joinToString(separator = ", ", transform = PrivacyItem::toLog))
privacyList = list
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
new file mode 100644
index 000000000000..c88676e713b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.PrivacyLog
+import javax.inject.Inject
+
+private const val TAG = "PrivacyLog"
+
+class PrivacyLogger @Inject constructor(
+ @PrivacyLog private val buffer: LogBuffer
+) {
+
+ fun logUpdatedItemFromAppOps(code: Int, uid: Int, packageName: String, active: Boolean) {
+ log(LogLevel.INFO, {
+ int1 = code
+ int2 = uid
+ str1 = packageName
+ bool1 = active
+ }, {
+ "App Op: $int1 for $str1($int2), active=$bool1"
+ })
+ }
+
+ fun logUpdatedPrivacyItemsList(listAsString: String) {
+ log(LogLevel.INFO, {
+ str1 = listAsString
+ }, {
+ "Updated list: $str1"
+ })
+ }
+
+ fun logCurrentProfilesChanged(profiles: List<Int>) {
+ log(LogLevel.INFO, {
+ str1 = profiles.toString()
+ }, {
+ "Profiles changed: $str1"
+ })
+ }
+
+ fun logChipVisible(visible: Boolean) {
+ log(LogLevel.INFO, {
+ bool1 = visible
+ }, {
+ "Chip visible: $bool1"
+ })
+ }
+
+ fun logStatusBarIconsVisible(
+ showCamera: Boolean,
+ showMichrophone: Boolean,
+ showLocation: Boolean
+ ) {
+ log(LogLevel.INFO, {
+ bool1 = showCamera
+ bool2 = showMichrophone
+ bool3 = showLocation
+ }, {
+ "Status bar icons visible: camera=$bool1, microphone=$bool2, location=$bool3"
+ })
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7e2433a1fd33..8e0e4ac7c8ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -205,12 +205,11 @@ public class QSContainerImpl extends FrameLayout {
mQSPanelContainer.setLayoutParams(layoutParams);
mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
- mContentPaddingStart = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_start);
- int newPaddingEnd = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- boolean marginsChanged = newPaddingEnd != mContentPaddingEnd;
- mContentPaddingEnd = newPaddingEnd;
+ int padding = getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_shade_content_margin_horizontal);
+ boolean marginsChanged = padding != mContentPaddingStart || padding != mContentPaddingEnd;
+ mContentPaddingStart = padding;
+ mContentPaddingEnd = padding;
if (marginsChanged) {
updatePaddingsAndMargins();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 44f847c34e2b..32904a21cd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -43,6 +43,7 @@ import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.privacy.PrivacyChipEvent;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
@@ -90,6 +91,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final StatusIconContainer mIconContainer;
private final StatusBarIconController.TintedIconManager mIconManager;
private final DemoMode mDemoModeReceiver;
+ private final PrivacyLogger mPrivacyLogger;
private boolean mListening;
private AlarmClockInfo mNextAlarm;
@@ -213,7 +215,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
CommandQueue commandQueue, DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
- QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
+ PrivacyLogger privacyLogger) {
super(view);
mZenModeController = zenModeController;
mNextAlarmController = nextAlarmController;
@@ -228,6 +231,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
mHeaderQsPanelController = quickQSPanelController;
+ mPrivacyLogger = privacyLogger;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
@@ -323,6 +327,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private void setChipVisibility(boolean chipVisible) {
if (chipVisible && getChipEnabled()) {
mPrivacyChip.setVisibility(View.VISIBLE);
+ mPrivacyLogger.logChipVisible(true);
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
if (!mPrivacyChipLogged && mListening) {
@@ -330,6 +335,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
}
} else {
+ mPrivacyLogger.logChipVisible(false);
mPrivacyChip.setVisibility(View.GONE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index c0061ad97293..b2ebf3f700b9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -281,8 +281,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// cancel current pending intent (if any) since clipData isn't used for matching
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
- sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ context, 0, sharingChooserIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null, UserHandle.CURRENT);
// Create a share action for the notification
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
@@ -294,7 +296,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mSmartActionsEnabled)
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
@@ -323,7 +326,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
- editIntent, 0, null, UserHandle.CURRENT);
+ editIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
@@ -338,7 +341,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mSmartActionsEnabled)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
@@ -360,7 +364,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_CANCEL_CURRENT
+ | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE);
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
r.getString(com.android.internal.R.string.delete), deleteAction);
@@ -401,7 +407,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
broadcastIntent).setContextual(true).addExtras(extras).build());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 160b6f78d9a3..71e1d12b610e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -336,7 +336,6 @@ public class NotificationContentView extends FrameLayout {
? contractedHeader.getPaddingLeft()
: paddingEnd,
contractedHeader.getPaddingBottom());
- contractedHeader.setShowWorkBadgeAtEnd(false);
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 0fc4c42599cf..5aeacaba6f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -22,8 +22,10 @@ import android.app.Notification;
import android.content.Context;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
+import android.view.NotificationTopLineView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
@@ -44,7 +46,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import java.util.Stack;
/**
- * Wraps a notification header view.
+ * Wraps a notification view which may or may not include a header.
*/
public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
@@ -56,6 +58,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
protected NotificationHeaderView mNotificationHeader;
+ protected NotificationTopLineView mNotificationTopLine;
private TextView mHeaderText;
private TextView mAppNameText;
private ImageView mWorkProfileImage;
@@ -107,14 +110,15 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
+ mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line);
mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
}
private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
View.OnClickListener listener = row.getFeedbackOnClickListener();
- if (mNotificationHeader != null) {
- mNotificationHeader.setFeedbackOnClickListener(listener);
+ if (mNotificationTopLine != null) {
+ mNotificationTopLine.setFeedbackOnClickListener(listener);
}
if (mFeedbackIcon != null) {
mFeedbackIcon.setOnClickListener(listener);
@@ -158,13 +162,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mAppNameText.setTextAppearance(
com.android.internal.R.style
.TextAppearance_DeviceDefault_Notification_Conversation_AppName);
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
layoutParams.setMarginStart(0);
}
if (mIconContainer != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width =
mIconContainer.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.conversation_content_start);
@@ -174,8 +176,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
layoutParams.setMarginStart(marginStart * -1);
}
if (mIcon != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams();
layoutParams.setMarginEnd(0);
}
}
@@ -187,21 +188,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
com.android.internal.R.attr.notificationHeaderTextAppearance,
com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
mAppNameText.setTextAppearance(textAppearance);
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_app_name_margin_start);
layoutParams.setMarginStart(marginStart);
}
if (mIconContainer != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.setMarginStart(0);
}
if (mIcon != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams();
final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_icon_margin_end);
layoutParams.setMarginEnd(marginEnd);
@@ -261,6 +259,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
@Override
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 6ed092f33d95..d53724159244 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -50,6 +50,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyType;
+import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
@@ -145,6 +146,7 @@ public class PhoneStatusBarPolicy
private final SensorPrivacyController mSensorPrivacyController;
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
+ private final PrivacyLogger mPrivacyLogger;
private boolean mZenVisible;
private boolean mVolumeVisible;
@@ -172,7 +174,8 @@ public class PhoneStatusBarPolicy
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker,
- PrivacyItemController privacyItemController) {
+ PrivacyItemController privacyItemController,
+ PrivacyLogger privacyLogger) {
mIconController = iconController;
mCommandQueue = commandQueue;
mBroadcastDispatcher = broadcastDispatcher;
@@ -197,6 +200,7 @@ public class PhoneStatusBarPolicy
mUiBgExecutor = uiBgExecutor;
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
+ mPrivacyLogger = privacyLogger;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -675,6 +679,7 @@ public class PhoneStatusBarPolicy
|| mPrivacyItemController.getLocationAvailable()) {
mIconController.setIconVisibility(mSlotLocation, showLocation);
}
+ mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
index cd94f8444c45..c401fab1e1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -23,6 +23,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
@@ -65,6 +66,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var logger: PrivacyLogger
private lateinit var privacyItemController: PrivacyItemController
private lateinit var executor: FakeExecutor
@@ -77,8 +80,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
executor,
deviceConfigProxy,
userTracker,
- dumpManager
- )
+ logger,
+ dumpManager)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 16a11050f9bb..3e834986e383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -29,10 +29,12 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.time.FakeSystemClock
import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.not
@@ -86,6 +88,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
private lateinit var userTracker: UserTracker
@Mock
private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var logger: PrivacyLogger
@Captor
private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
@Captor
@@ -102,8 +106,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
executor,
deviceConfigProxy,
userTracker,
- dumpManager
- )
+ logger,
+ dumpManager)
}
@Before
@@ -300,6 +304,45 @@ class PrivacyItemControllerTest : SysuiTestCase() {
verify(callback, never()).onPrivacyItemsChanged(any())
}
+ @Test
+ fun testLogActiveChanged() {
+ privacyItemController.addCallback(callback)
+ executor.runAllReady()
+
+ verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+ argCaptorCallback.value.onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+
+ verify(logger).logUpdatedItemFromAppOps(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+ }
+
+ @Test
+ fun testLogListUpdated() {
+ doReturn(listOf(
+ AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0))
+ ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+ privacyItemController.addCallback(callback)
+ executor.runAllReady()
+
+ verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+ argCaptorCallback.value.onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+ executor.runAllReady()
+
+ val expected = PrivacyItem(
+ PrivacyType.TYPE_LOCATION,
+ PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID)
+ )
+
+ val captor = argumentCaptor<String>()
+ verify(logger, atLeastOnce()).logUpdatedPrivacyItemsList(capture(captor))
+ // Let's look at the last log
+ val values = captor.allValues
+ assertTrue(values[values.size - 1].contains(expected.toLog()))
+ }
+
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 226995198843..26b5d26387b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.demomode.DemoModeController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
@@ -86,6 +87,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock
private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock
+ private lateinit var privacyLogger: PrivacyLogger
+ @Mock
private lateinit var iconContainer: StatusIconContainer
@Mock
private lateinit var qsCarrierGroup: QSCarrierGroup
@@ -123,7 +126,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
demoModeController,
userTracker,
quickQSPanelController,
- qsCarrierGroupControllerBuilder
+ qsCarrierGroupControllerBuilder,
+ privacyLogger
)
}
diff --git a/packages/Tethering/tests/Android.bp b/packages/Tethering/tests/Android.bp
new file mode 100644
index 000000000000..cb0a20bdf0e8
--- /dev/null
+++ b/packages/Tethering/tests/Android.bp
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+filegroup {
+ name: "TetheringTestsJarJarRules",
+ srcs: ["jarjar-rules.txt"],
+ visibility: [
+ "//frameworks/base/packages/Tethering/tests:__subpackages__",
+ ]
+}
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 02bab9ba353e..5765c01c43f3 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -79,6 +79,7 @@ android_test {
// For NetworkStackUtils included in NetworkStackBase
"libnetworkstackutilsjni",
],
+ jarjar_rules: ":TetheringTestsJarJarRules",
compile_multilib: "both",
manifest: "AndroidManifest_coverage.xml",
-} \ No newline at end of file
+}
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/jarjar-rules.txt
index 7ed89632a861..c99ff7f81877 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/jarjar-rules.txt
@@ -10,7 +10,10 @@ rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.t
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
+
# TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains.
# TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils.
zap android.os.test.TestLooperTest*
-zap com.android.test.filters.SelectTestTests* \ No newline at end of file
+zap com.android.test.filters.SelectTestTests*
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 04137145a2b0..ef556cf92392 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -68,7 +68,6 @@ java_defaults {
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
],
- jarjar_rules: "jarjar-rules.txt",
}
// Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -89,6 +88,7 @@ android_test {
"device-tests",
"mts",
],
+ jarjar_rules: ":TetheringTestsJarJarRules",
defaults: ["TetheringTestsDefaults"],
compile_multilib: "both",
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 4c8925bd4828..70cf04522c45 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1122,4 +1122,8 @@ public abstract class PackageManagerInternal {
public abstract IncrementalStatesInfo getIncrementalStatesInfo(String packageName,
int filterCallingUid, int userId);
+ /**
+ * Notifies that a package has crashed or ANR'd.
+ */
+ public abstract void notifyPackageCrashOrAnr(String packageName);
}
diff --git a/services/core/java/com/android/server/Dumpable.java b/services/core/java/com/android/server/Dumpable.java
new file mode 100644
index 000000000000..d2bd66f59b62
--- /dev/null
+++ b/services/core/java/com/android/server/Dumpable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+/**
+ * Interface used to dump {@link SystemServer} state that is not associated with any service.
+ */
+public interface Dumpable {
+
+ /**
+ * Dumps the state.
+ */
+ void dump(@NonNull IndentingPrintWriter pw, @Nullable String[] args);
+
+ /**
+ * Gets the name of the dumpable.
+ *
+ * <p>If not overridden, will return the simple class name.
+ */
+ default String getDumpableName() {
+ return Dumpable.this.getClass().getSimpleName();
+ }
+}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 20f68da1592e..d4e912b65e17 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -75,9 +75,9 @@ public class GestureLauncherService extends SystemService {
@VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500;
/**
- * Number of taps required to launch panic ui.
+ * Number of taps required to launch emergency gesture ui.
*/
- private static final int PANIC_POWER_TAP_COUNT_THRESHOLD = 5;
+ private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5;
/**
* Number of taps required to launch camera shortcut.
@@ -138,9 +138,9 @@ public class GestureLauncherService extends SystemService {
private boolean mCameraDoubleTapPowerEnabled;
/**
- * Whether panic button gesture is currently enabled
+ * Whether emergency gesture is currently enabled
*/
- private boolean mPanicButtonGestureEnabled;
+ private boolean mEmergencyGestureEnabled;
private long mLastPowerDown;
private int mPowerButtonConsecutiveTaps;
@@ -178,7 +178,7 @@ public class GestureLauncherService extends SystemService {
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- updatePanicButtonGestureEnabled();
+ updateEmergencyGestureEnabled();
mUserId = ActivityManager.getCurrentUser();
mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
@@ -197,7 +197,7 @@ public class GestureLauncherService extends SystemService {
Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.PANIC_GESTURE_ENABLED),
+ Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED),
false, mSettingObserver, mUserId);
}
@@ -225,10 +225,10 @@ public class GestureLauncherService extends SystemService {
}
@VisibleForTesting
- void updatePanicButtonGestureEnabled() {
- boolean enabled = isPanicButtonGestureEnabled(mContext, mUserId);
+ void updateEmergencyGestureEnabled() {
+ boolean enabled = isEmergencyGestureEnabled(mContext, mUserId);
synchronized (this) {
- mPanicButtonGestureEnabled = enabled;
+ mEmergencyGestureEnabled = enabled;
}
}
@@ -357,11 +357,11 @@ public class GestureLauncherService extends SystemService {
}
/**
- * Whether to enable panic button gesture.
+ * Whether to enable emergency gesture.
*/
- public static boolean isPanicButtonGestureEnabled(Context context, int userId) {
+ public static boolean isEmergencyGestureEnabled(Context context, int userId) {
return Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.PANIC_GESTURE_ENABLED, 0, userId) != 0;
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED, 0, userId) != 0;
}
/**
@@ -409,7 +409,7 @@ public class GestureLauncherService extends SystemService {
return false;
}
boolean launchCamera = false;
- boolean launchPanic = false;
+ boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
synchronized (this) {
@@ -428,15 +428,15 @@ public class GestureLauncherService extends SystemService {
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
}
- // Check if we need to launch camera or panic flows
- if (mPanicButtonGestureEnabled) {
+ // Check if we need to launch camera or emergency gesture flows
+ if (mEmergencyGestureEnabled) {
// Commit to intercepting the powerkey event after the second "quick" tap to avoid
- // lockscreen changes between launching camera and the panic flow.
+ // lockscreen changes between launching camera and the emergency gesture flow.
if (mPowerButtonConsecutiveTaps > 1) {
intercept = interactive;
}
- if (mPowerButtonConsecutiveTaps == PANIC_POWER_TAP_COUNT_THRESHOLD) {
- launchPanic = true;
+ if (mPowerButtonConsecutiveTaps == EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD) {
+ launchEmergencyGesture = true;
}
}
if (mCameraDoubleTapPowerEnabled
@@ -461,18 +461,18 @@ public class GestureLauncherService extends SystemService {
mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
(int) powerTapInterval);
}
- } else if (launchPanic) {
- Slog.i(TAG, "Panic gesture detected, launching panic.");
- launchPanic = handlePanicButtonGesture();
+ } else if (launchEmergencyGesture) {
+ Slog.i(TAG, "Emergency gesture detected, launching.");
+ launchEmergencyGesture = handleEmergencyGesture();
// TODO(b/160006048): Add logging
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
- outLaunched.value = launchCamera || launchPanic;
- // Intercept power key event if the press is part of a gesture (camera, panic) and the user
- // has completed setup.
+ outLaunched.value = launchCamera || launchEmergencyGesture;
+ // Intercept power key event if the press is part of a gesture (camera, eGesture) and the
+ // user has completed setup.
return intercept && isUserSetupComplete();
}
@@ -512,27 +512,25 @@ public class GestureLauncherService extends SystemService {
}
/**
- * @return true if panic ui was launched, false otherwise.
+ * @return true if emergency gesture UI was launched, false otherwise.
*/
@VisibleForTesting
- boolean handlePanicButtonGesture() {
- // TODO(b/160006048): This is the wrong way to launch panic ui. Rewrite this to go
- // through SysUI
+ boolean handleEmergencyGesture() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- "GestureLauncher:handlePanicButtonGesture");
+ "GestureLauncher:handleEmergencyGesture");
try {
boolean userSetupComplete = isUserSetupComplete();
if (!userSetupComplete) {
if (DBG) {
Slog.d(TAG, String.format(
- "userSetupComplete = %s, ignoring panic gesture.",
+ "userSetupComplete = %s, ignoring emergency gesture.",
userSetupComplete));
}
return false;
}
if (DBG) {
Slog.d(TAG, String.format(
- "userSetupComplete = %s, performing panic gesture.",
+ "userSetupComplete = %s, performing emergency gesture.",
userSetupComplete));
}
StatusBarManagerInternal service = LocalServices.getService(
@@ -558,7 +556,7 @@ public class GestureLauncherService extends SystemService {
registerContentObservers();
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- updatePanicButtonGestureEnabled();
+ updateEmergencyGestureEnabled();
}
}
};
@@ -568,7 +566,7 @@ public class GestureLauncherService extends SystemService {
if (userId == mUserId) {
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- updatePanicButtonGestureEnabled();
+ updateEmergencyGestureEnabled();
}
}
};
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index c0611374679b..c23f1cab0614 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -19,6 +19,7 @@ package com.android.server;
import android.annotation.NonNull;
import android.os.Build;
import android.os.Process;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -44,7 +45,7 @@ import java.util.concurrent.TimeUnit;
*
* @hide
*/
-public class SystemServerInitThreadPool {
+public final class SystemServerInitThreadPool implements Dumpable {
private static final String TAG = SystemServerInitThreadPool.class.getSimpleName();
private static final int SHUTDOWN_TIMEOUT_MILLIS = 20000;
private static final boolean IS_DEBUGGABLE = Build.IS_DEBUGGABLE;
@@ -53,6 +54,7 @@ public class SystemServerInitThreadPool {
@GuardedBy("LOCK")
private static SystemServerInitThreadPool sInstance;
+ private final int mSize; // used by dump() only
private final ExecutorService mService;
@GuardedBy("mPendingTasks")
@@ -62,9 +64,9 @@ public class SystemServerInitThreadPool {
private boolean mShutDown;
private SystemServerInitThreadPool() {
- final int size = Runtime.getRuntime().availableProcessors();
- Slog.i(TAG, "Creating instance with " + size + " threads");
- mService = ConcurrentUtils.newFixedThreadPool(size,
+ mSize = Runtime.getRuntime().availableProcessors();
+ Slog.i(TAG, "Creating instance with " + mSize + " threads");
+ mService = ConcurrentUtils.newFixedThreadPool(mSize,
"system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
}
@@ -123,11 +125,13 @@ public class SystemServerInitThreadPool {
*
* @throws IllegalStateException if it has been started already without being shut down yet.
*/
- static void start() {
+ static SystemServerInitThreadPool start() {
+ SystemServerInitThreadPool instance;
synchronized (LOCK) {
Preconditions.checkState(sInstance == null, TAG + " already started");
- sInstance = new SystemServerInitThreadPool();
+ instance = sInstance = new SystemServerInitThreadPool();
}
+ return instance;
}
/**
@@ -190,4 +194,22 @@ public class SystemServerInitThreadPool {
ActivityManagerService.dumpStackTraces(pids, null, null,
Watchdog.getInterestingNativePids(), null);
}
+
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ synchronized (LOCK) {
+ pw.printf("has instance: %b\n", (sInstance != null));
+ }
+ pw.printf("number of threads: %d\n", mSize);
+ pw.printf("service: %s\n", mService);
+ synchronized (mPendingTasks) {
+ pw.printf("is shutdown: %b\n", mShutDown);
+ final int pendingTasks = mPendingTasks.size();
+ if (pendingTasks == 0) {
+ pw.println("no pending tasks");
+ } else {
+ pw.printf("%d pending tasks: %s\n", pendingTasks, mPendingTasks);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 84d01ec3598d..6c81de6af402 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -200,21 +200,21 @@ public abstract class SystemService {
/**
* @hide
*/
- public void dump(@NonNull StringBuilder builder) {
- builder.append(getUserIdentifier());
+ public void dump(@NonNull PrintWriter pw) {
+ pw.print(getUserIdentifier());
if (!isFull() && !isManagedProfile()) return;
- builder.append('(');
+ pw.print('(');
boolean addComma = false;
if (isFull()) {
- builder.append("full");
+ pw.print("full");
}
if (isManagedProfile()) {
- if (addComma) builder.append(',');
- builder.append("mp");
+ if (addComma) pw.print(',');
+ pw.print("mp");
}
- builder.append(')');
+ pw.print(')');
}
}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index ff2661b19b48..71a18218110e 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -27,6 +27,7 @@ import android.os.Trace;
import android.os.UserManagerInternal;
import android.util.ArrayMap;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -49,7 +50,7 @@ import java.util.ArrayList;
*
* {@hide}
*/
-public final class SystemServiceManager {
+public final class SystemServiceManager implements Dumpable {
private static final String TAG = SystemServiceManager.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int SERVICE_CALL_WARN_TIME_MS = 50;
@@ -489,31 +490,39 @@ public final class SystemServiceManager {
return sSystemDir;
}
- /**
- * Outputs the state of this manager to the System log.
- */
- public void dump() {
- StringBuilder builder = new StringBuilder();
- builder.append("Current phase: ").append(mCurrentPhase).append('\n');
- builder.append("Services:\n");
- final int startedLen = mServices.size();
- for (int i = 0; i < startedLen; i++) {
- final SystemService service = mServices.get(i);
- builder.append("\t")
- .append(service.getClass().getSimpleName())
- .append("\n");
- }
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ pw.printf("Current phase: %d\n", mCurrentPhase);
synchronized (mTargetUsers) {
- builder.append("Current user: ").append(mCurrentUser).append('\n');
- builder.append("Target users: ");
+ if (mCurrentUser != null) {
+ pw.print("Current user: "); mCurrentUser.dump(pw); pw.println();
+ } else {
+ pw.println("Current user not set!");
+ }
+
final int targetUsersSize = mTargetUsers.size();
- for (int i = 0; i < targetUsersSize; i++) {
- mTargetUsers.valueAt(i).dump(builder);
- if (i != targetUsersSize - 1) builder.append(',');
+ if (targetUsersSize > 0) {
+ pw.printf("%d target users: ", targetUsersSize);
+ for (int i = 0; i < targetUsersSize; i++) {
+ mTargetUsers.valueAt(i).dump(pw);
+ if (i != targetUsersSize - 1) pw.print(", ");
+ }
+ pw.println();
+ } else {
+ pw.println("No target users");
}
- builder.append('\n');
}
-
- Slog.e(TAG, builder.toString());
+ final int startedLen = mServices.size();
+ if (startedLen > 0) {
+ pw.printf("%d started services:\n", startedLen);
+ pw.increaseIndent();
+ for (int i = 0; i < startedLen; i++) {
+ final SystemService service = mServices.get(i);
+ pw.println(service.getClass().getCanonicalName());
+ }
+ pw.decreaseIndent();
+ } else {
+ pw.println("No started services");
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 32d95f594ce9..51cbfcf64322 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7595,6 +7595,10 @@ public class ActivityManagerService extends IActivityManager.Stub
eventType, r, processName, null, null, null, null, null, null, crashInfo);
mAppErrors.crashApplication(r, crashInfo);
+ // Notify package manager service to possibly update package state
+ if (r != null && r.info != null && r.info.packageName != null) {
+ mPackageManagerInt.notifyPackageCrashOrAnr(r.info.packageName);
+ }
}
public void handleApplicationStrictModeViolation(
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 53c6758585cf..ccdd6a746239 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1764,6 +1764,12 @@ class ProcessRecord implements WindowProcessListener {
makeAppNotRespondingLocked(activityShortComponentName,
annotation != null ? "ANR " + annotation : "ANR", info.toString());
+ // Notify package manager service to possibly update package state
+ if (aInfo != null && aInfo.packageName != null) {
+ mService.getPackageManagerInternalLocked().notifyPackageCrashOrAnr(
+ aInfo.packageName);
+ }
+
// mUiHandler can be null if the AMS is constructed with injector only. This will only
// happen in tests.
if (mService.mUiHandler != null) {
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
new file mode 100644
index 000000000000..36acc3c7344d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsBiometricsTestCases"
+ }
+ ]
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 5dcadee20e13..9ac12ed11ded 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -52,11 +52,10 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.internal.R;
@@ -92,55 +91,6 @@ public class FingerprintService extends SystemService {
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull private List<ServiceProvider> mServiceProviders;
- @NonNull private final ArrayMap<Integer, TestSession> mTestSessions;
-
- private final class TestSession extends ITestSession.Stub {
- private final int mSensorId;
-
- TestSession(int sensorId) {
- mSensorId = sensorId;
- }
-
- @Override
- public void enableTestHal(boolean enableTestHal) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void startEnroll(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void finishEnroll(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void acceptAuthentication(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void rejectAuthentication(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void notifyAcquired(int userId, int acquireInfo) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void notifyError(int userId, int errorCode) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
-
- @Override
- public void cleanupInternalState(int userId) {
- Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- }
- }
/**
* Receives the incoming binder calls from FingerprintManager.
@@ -150,20 +100,22 @@ public class FingerprintService extends SystemService {
public ITestSession createTestSession(int sensorId, String opPackageName) {
Utils.checkPermission(getContext(), TEST_BIOMETRIC);
- final TestSession session;
- synchronized (mTestSessions) {
- if (!mTestSessions.containsKey(sensorId)) {
- mTestSessions.put(sensorId, new TestSession(sensorId));
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider.createTestSession(sensorId, opPackageName);
}
- session = mTestSessions.get(sensorId);
}
- return session;
+
+ return null;
}
@Override // Binder call
public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ if (getContext().checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+ != PackageManager.PERMISSION_GRANTED) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
final List<FingerprintSensorPropertiesInternal> properties =
FingerprintService.this.getSensorProperties();
@@ -424,12 +376,28 @@ public class FingerprintService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
- for (ServiceProvider provider : mServiceProviders) {
- for (FingerprintSensorPropertiesInternal props :
- provider.getSensorProperties()) {
- if (args.length > 0 && "--proto".equals(args[0])) {
- provider.dumpProto(props.sensorId, fd);
- } else {
+ if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props
+ : provider.getSensorProperties()) {
+ provider.dumpProtoState(props.sensorId, proto);
+ }
+ }
+ proto.flush();
+ } else if (args.length > 0 && "--proto".equals(args[0])) {
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props
+ : provider.getSensorProperties()) {
+ provider.dumpProtoMetrics(props.sensorId, fd);
+ }
+ }
+ } else {
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props
+ : provider.getSensorProperties()) {
+ pw.println("Dumping for sensorId: " + props.sensorId
+ + ", provider: " + provider.getClass().getSimpleName());
provider.dumpInternal(props.sensorId, pw);
}
}
@@ -622,7 +590,6 @@ public class FingerprintService extends SystemService {
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mServiceProviders = new ArrayList<>();
- mTestSessions = new ArrayMap<>();
initializeAidlHals();
}
@@ -648,7 +615,7 @@ public class FingerprintService extends SystemService {
try {
final SensorProps[] props = fp.getSensorProps();
final FingerprintProvider provider =
- new FingerprintProvider(getContext(), props, fqName,
+ new FingerprintProvider(getContext(), props, instance,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index c2315fdd4ccc..1ed66a247bd0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -19,11 +19,13 @@ package com.android.server.biometrics.sensors.fingerprint;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.biometrics.ITestSession;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -110,7 +112,11 @@ public interface ServiceProvider {
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
- void dumpProto(int sensorId, @NonNull FileDescriptor fd);
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto);
+
+ void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+
+ @NonNull ITestSession createTestSession(int sensorId, @NonNull String opPackageName);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
new file mode 100644
index 000000000000..6bb40e6630bf
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static android.Manifest.permission.TEST_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * A test session implementation for {@link FingerprintProvider}. See
+ * {@link android.hardware.biometrics.BiometricTestSession}.
+ */
+class BiometricTestSessionImpl extends ITestSession.Stub {
+
+ private static final String TAG = "BiometricTestSessionImpl";
+
+ @NonNull private final Context mContext;
+ private final int mSensorId;
+ @NonNull private final FingerprintProvider mProvider;
+ @NonNull private final Sensor mSensor;
+ @NonNull private final Set<Integer> mEnrollmentIds;
+ @NonNull private final Random mRandom;
+
+ /**
+ * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
+ * test, since enrollment is a platform-only API. The authentication path is tested through
+ * the public FingerprintManager APIs and does not use this receiver.
+ */
+ private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
+ @Override
+ public void onEnrollResult(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(Fingerprint fp, int userId,
+ boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+
+ }
+
+ @Override
+ public void onError(int error, int vendorCode) {
+
+ }
+
+ @Override
+ public void onRemoved(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onChallengeGenerated(int sensorId, long challenge) {
+
+ }
+ };
+
+ BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+ @NonNull FingerprintProvider provider, @NonNull Sensor sensor) {
+ mContext = context;
+ mSensorId = sensorId;
+ mProvider = provider;
+ mSensor = sensor;
+ mEnrollmentIds = new HashSet<>();
+ mRandom = new Random();
+ }
+
+ @Override
+ public void setTestHalEnabled(boolean enabled) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mProvider.setTestHalEnabled(enabled);
+ mSensor.setTestHalEnabled(enabled);
+ }
+
+ @Override
+ public void startEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
+ mContext.getOpPackageName(), null /* surface */);
+ }
+
+ @Override
+ public void finishEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ int nextRandomId = mRandom.nextInt();
+ while (mEnrollmentIds.contains(nextRandomId)) {
+ nextRandomId = mRandom.nextInt();
+ }
+
+ mEnrollmentIds.add(nextRandomId);
+ mSensor.getSessionForUser(userId).mHalSessionCallback
+ .onEnrollmentProgress(nextRandomId, 0 /* remaining */);
+ }
+
+ @Override
+ public void acceptAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ // Fake authentication with any of the existing fingers
+ List<Fingerprint> fingerprints = FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId);
+ if (fingerprints.isEmpty()) {
+ Slog.w(TAG, "No fingerprints, returning");
+ return;
+ }
+ final int fid = fingerprints.get(0).getBiometricId();
+ mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid,
+ HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69]));
+ }
+
+ @Override
+ public void rejectAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
+ }
+
+ @Override
+ public void notifyAcquired(int userId, int acquireInfo) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mSensor.getSessionForUser(userId).mHalSessionCallback
+ .onAcquired((byte) acquireInfo, 0 /* vendorCode */);
+ }
+
+ @Override
+ public void notifyError(int userId, int errorCode) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode,
+ 0 /* vendorCode */);
+ }
+
+ @Override
+ public void cleanupInternalState(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mProvider.scheduleInternalCleanup(mSensorId, userId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index fec3cff6d52f..2ad1fa306781 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -47,6 +47,11 @@ class FingerprintGetAuthenticatorIdClient extends ClientMonitor<ISession> {
// Nothing to do here
}
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
@Override
protected void startHalOperation() {
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index d713f981b451..4d07f583fdf5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -24,6 +24,7 @@ import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
@@ -38,6 +39,7 @@ import android.os.ServiceManager;
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.server.biometrics.Utils;
@@ -62,6 +64,8 @@ import java.util.List;
@SuppressWarnings("deprecation")
public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
+ private boolean mTestHalEnabled;
+
@NonNull private final Context mContext;
@NonNull private final String mHalInstanceName;
@NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -132,7 +136,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
prop.commonProps.maxEnrollmentsPerUser,
prop.sensorType,
true /* resetLockoutRequiresHardwareAuthToken */);
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, mContext, mHandler,
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, gestureAvailabilityDispatcher);
mSensors.put(sensorId, sensor);
@@ -146,6 +150,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@Nullable
private synchronized IFingerprint getHalInstance() {
+ if (mTestHalEnabled) {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+
if (mDaemon != null) {
return mDaemon;
}
@@ -153,7 +164,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
Slog.d(getTag(), "Daemon was null, reconnecting");
mDaemon = IFingerprint.Stub.asInterface(
- ServiceManager.waitForDeclaredService(mHalInstanceName));
+ ServiceManager.waitForDeclaredService(IFingerprint.DESCRIPTOR
+ + "/" + mHalInstanceName));
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -561,7 +573,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
- public void dumpProto(int sensorId, @NonNull FileDescriptor fd) {
+ public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+ if (mSensors.contains(sensorId)) {
+ mSensors.get(sensorId).dumpProtoState(sensorId, proto);
+ }
+ }
+
+ @Override
+ public void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd) {
}
@@ -570,6 +589,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
+ @NonNull
+ @Override
+ public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+ return mSensors.get(sensorId).createTestSession();
+ }
+
@Override
public void binderDied() {
Slog.e(getTag(), "HAL died");
@@ -582,4 +607,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
});
}
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index d4ce896d42ec..51c30b68e0c5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -19,7 +19,9 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
@@ -31,11 +33,16 @@ import android.hardware.keymaster.HardwareAuthToken;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.fingerprint.FingerprintServiceStateProto;
+import com.android.server.biometrics.fingerprint.SensorStateProto;
+import com.android.server.biometrics.fingerprint.UserStateProto;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -48,9 +55,7 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -59,7 +64,11 @@ import java.util.Map;
*/
@SuppressWarnings("deprecation")
class Sensor implements IBinder.DeathRecipient {
+
+ private boolean mTestHalEnabled;
+
@NonNull private final String mTag;
+ @NonNull private final FingerprintProvider mProvider;
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
@@ -91,320 +100,377 @@ class Sensor implements IBinder.DeathRecipient {
});
}
- private static class Session {
+ static class Session {
@NonNull private final String mTag;
@NonNull private final ISession mSession;
private final int mUserId;
- private final ISessionCallback mSessionCallback;
+ @NonNull final HalSessionCallback mHalSessionCallback;
Session(@NonNull String tag, @NonNull ISession session, int userId,
- @NonNull ISessionCallback sessionCallback) {
+ @NonNull HalSessionCallback halSessionCallback) {
mTag = tag;
mSession = session;
mUserId = userId;
- mSessionCallback = sessionCallback;
+ mHalSessionCallback = halSessionCallback;
Slog.d(mTag, "New session created for user: " + userId);
}
}
- Sensor(@NonNull String tag, @NonNull Context context, @NonNull Handler handler,
- @NonNull FingerprintSensorPropertiesInternal sensorProperties,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- mTag = tag;
- mContext = context;
- mHandler = handler;
- mSensorProperties = sensorProperties;
- mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
- mLockoutCache = new LockoutCache();
- mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
- }
-
- @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() {
- return mLazySession;
- }
-
- @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
- return mSensorProperties;
- }
-
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean hasSessionForUser(int userId) {
- return mCurrentSession != null && mCurrentSession.mUserId == userId;
- }
-
- void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
- throws RemoteException {
- final ISessionCallback callback = new ISessionCallback.Stub() {
- @Override
- public void onStateChanged(int cookie, byte state) {
- // TODO(b/162973174)
- }
-
- @Override
- public void onChallengeGenerated(long challenge) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGenerateChallengeClient)) {
- Slog.e(mTag, "onChallengeGenerated for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGenerateChallengeClient generateChallengeClient =
- (FingerprintGenerateChallengeClient) client;
- generateChallengeClient.onChallengeGenerated(sensorId, userId, challenge);
- });
- }
+ static class HalSessionCallback extends ISessionCallback.Stub {
- @Override
- public void onChallengeRevoked(long challenge) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintRevokeChallengeClient)) {
- Slog.e(mTag, "onChallengeRevoked for wrong client: "
- + Utils.getClientName(client));
- return;
- }
+ /**
+ * Interface to sends results to the HalSessionCallback's owner.
+ */
+ public interface Callback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
- final FingerprintRevokeChallengeClient revokeChallengeClient =
- (FingerprintRevokeChallengeClient) client;
- revokeChallengeClient.onChallengeRevoked(sensorId, userId, challenge);
- });
- }
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final String mTag;
+ @NonNull private final BiometricScheduler mScheduler;
+ private final int mSensorId;
+ private final int mUserId;
+ @NonNull private final Callback mCallback;
- @Override
- public void onAcquired(byte info, int vendorCode) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(mTag, "onAcquired for non-acquisition client: "
- + Utils.getClientName(client));
- return;
- }
+ HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull Callback callback) {
+ mContext = context;
+ mHandler = handler;
+ mTag = tag;
+ mScheduler = scheduler;
+ mSensorId = sensorId;
+ mUserId = userId;
+ mCallback = callback;
+ }
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(info, vendorCode);
- });
- }
+ @Override
+ public void onStateChanged(int cookie, byte state) {
+ // TODO(b/162973174)
+ }
- @Override
- public void onError(byte error, int vendorCode) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- Slog.d(mTag, "onError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof Interruptable)) {
- Slog.e(mTag, "onError for non-error consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @Override
+ public void onChallengeGenerated(long challenge) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintGenerateChallengeClient)) {
+ Slog.e(mTag, "onChallengeGenerated for wrong client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintGenerateChallengeClient generateChallengeClient =
+ (FingerprintGenerateChallengeClient) client;
+ generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
+ });
+ }
- final Interruptable interruptable = (Interruptable) client;
- interruptable.onError(error, vendorCode);
+ @Override
+ public void onChallengeRevoked(long challenge) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintRevokeChallengeClient)) {
+ Slog.e(mTag, "onChallengeRevoked for wrong client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintRevokeChallengeClient revokeChallengeClient =
+ (FingerprintRevokeChallengeClient) client;
+ revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
+ });
+ }
- if (error == Error.HW_UNAVAILABLE) {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- }
- });
- }
+ @Override
+ public void onAcquired(byte info, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(mTag, "onAcquired for non-acquisition client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(info, vendorCode);
+ });
+ }
- @Override
- public void onEnrollmentProgress(int enrollmentId, int remaining) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintEnrollClient)) {
- Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
+ @Override
+ public void onError(byte error, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ Slog.d(mTag, "onError"
+ + ", client: " + Utils.getClientName(client)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof Interruptable)) {
+ Slog.e(mTag, "onError for non-error consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FingerprintUtils.getInstance(sensorId)
- .getUniqueName(mContext, currentUserId);
- final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, sensorId);
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(error, vendorCode);
- final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
- enrollClient.onEnrollResult(fingerprint, remaining);
- });
- }
+ if (error == Error.HW_UNAVAILABLE) {
+ mCallback.onHardwareUnavailable();
+ }
+ });
+ }
- @Override
- public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintEnrollClient)) {
+ Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final int currentUserId = client.getTargetUserId();
+ final CharSequence name = FingerprintUtils.getInstance(mSensorId)
+ .getUniqueName(mContext, currentUserId);
+ final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId);
+
+ final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
+ enrollClient.onEnrollResult(fingerprint, remaining);
+ });
+ }
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", enrollmentId, sensorId);
- final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
- final ArrayList<Byte> byteList = new ArrayList<>();
- for (byte b : byteArray) {
- byteList.add(b);
- }
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+
+ authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
+ });
+ }
- authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
- });
- }
+ @Override
+ public void onAuthenticationFailed() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
+ authenticationConsumer
+ .onAuthenticated(fp, false /* authenticated */, null /* hat */);
+ });
+ }
- @Override
- public void onAuthenticationFailed() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof LockoutConsumer)) {
+ Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+ lockoutConsumer.onLockoutTimed(durationMillis);
+ });
+ }
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, sensorId);
- authenticationConsumer
- .onAuthenticated(fp, false /* authenticated */, null /* hat */);
- });
- }
+ @Override
+ public void onLockoutPermanent() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof LockoutConsumer)) {
+ Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+ lockoutConsumer.onLockoutPermanent();
+ });
+ }
- @Override
- public void onLockoutTimed(long durationMillis) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @Override
+ public void onLockoutCleared() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintResetLockoutClient)) {
+ Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintResetLockoutClient resetLockoutClient =
+ (FingerprintResetLockoutClient) client;
+ resetLockoutClient.onLockoutCleared();
+ });
+ }
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutTimed(durationMillis);
- });
- }
+ @Override
+ public void onInteractionDetected() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintDetectClient)) {
+ Slog.e(mTag, "onInteractionDetected for non-detect client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintDetectClient fingerprintDetectClient =
+ (FingerprintDetectClient) client;
+ fingerprintDetectClient.onInteractionDetected();
+ });
+ }
- @Override
- public void onLockoutPermanent() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof EnumerateConsumer)) {
+ Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final EnumerateConsumer enumerateConsumer =
+ (EnumerateConsumer) client;
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
}
+ } else {
+ enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
+ }
+ });
+ }
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutPermanent();
- });
- }
-
- @Override
- public void onLockoutCleared() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintResetLockoutClient)) {
- Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
- + Utils.getClientName(client));
- return;
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof RemovalConsumer)) {
+ Slog.e(mTag, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
}
+ } else {
+ removalConsumer.onRemoved(null, 0);
+ }
+ });
+ }
- final FingerprintResetLockoutClient resetLockoutClient =
- (FingerprintResetLockoutClient) client;
- resetLockoutClient.onLockoutCleared();
- });
- }
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
+ Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
+ (FingerprintGetAuthenticatorIdClient) client;
+ getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
+ });
+ }
- @Override
- public void onInteractionDetected() {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintDetectClient)) {
- Slog.e(mTag, "onInteractionDetected for non-detect client: "
- + Utils.getClientName(client));
- return;
- }
+ @Override
+ public void onAuthenticatorIdInvalidated() {
+ // TODO(159667191)
+ }
+ }
- final FingerprintDetectClient fingerprintDetectClient =
- (FingerprintDetectClient) client;
- fingerprintDetectClient.onInteractionDetected();
- });
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ mTag = tag;
+ mProvider = provider;
+ mContext = context;
+ mHandler = handler;
+ mSensorProperties = sensorProperties;
+ mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
+ mLockoutCache = new LockoutCache();
+ mAuthenticatorIds = new HashMap<>();
+ mLazySession = () -> {
+ if (mTestHalEnabled) {
+ return new TestSession(mCurrentSession.mHalSessionCallback);
+ } else {
+ return mCurrentSession != null ? mCurrentSession.mSession : null;
}
+ };
+ }
- @Override
- public void onEnrollmentsEnumerated(int[] enrollmentIds) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() {
+ return mLazySession;
+ }
- final EnumerateConsumer enumerateConsumer =
- (EnumerateConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], sensorId);
- enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
- }
- } else {
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
- }
- });
- }
+ @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+ return mSensorProperties;
+ }
- @Override
- public void onEnrollmentsRemoved(int[] enrollmentIds) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(mTag, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ boolean hasSessionForUser(int userId) {
+ return mCurrentSession != null && mCurrentSession.mUserId == userId;
+ }
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], sensorId);
- removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
- }
- } else {
- removalConsumer.onRemoved(null, 0);
- }
- });
- }
+ @Nullable Session getSessionForUser(int userId) {
+ if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
+ return mCurrentSession;
+ } else {
+ return null;
+ }
+ }
- @Override
- public void onAuthenticatorIdRetrieved(long authenticatorId) {
- mHandler.post(() -> {
- final ClientMonitor<?> client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
- Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
+ @NonNull ITestSession createTestSession() {
+ return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this);
+ }
- final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
- (FingerprintGetAuthenticatorIdClient) client;
- getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
- });
- }
+ void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
+ throws RemoteException {
- @Override
- public void onAuthenticatorIdInvalidated() {
- // TODO(159667191)
- }
+ final HalSessionCallback.Callback callback = () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
};
+ final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
+ mTag, mScheduler, sensorId, userId, callback);
- final ISession newSession = daemon.createSession(sensorId, userId, callback);
+ final ISession newSession = daemon.createSession(sensorId, userId, resultController);
newSession.asBinder().linkToDeath(this, 0 /* flags */);
- mCurrentSession = new Session(mTag, newSession, userId, callback);
+ mCurrentSession = new Session(mTag, newSession, userId, resultController);
}
@NonNull BiometricScheduler getScheduler() {
@@ -418,4 +484,27 @@ class Sensor implements IBinder.DeathRecipient {
@NonNull Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
+
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+ final long sensorToken = proto.start(FingerprintServiceStateProto.SENSOR_STATES);
+
+ proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
+ proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(SensorStateProto.USER_STATES);
+ proto.write(UserStateProto.USER_ID, userId);
+ proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId).size());
+ proto.end(userToken);
+ }
+
+ proto.end(sensorToken);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
new file mode 100644
index 000000000000..8c9a269486bb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Test HAL that provides only provides no-ops.
+ */
+public class TestHal extends IFingerprint.Stub {
+ @Override
+ public SensorProps[] getSensorProps() {
+ return new SensorProps[0];
+ }
+
+ @Override
+ public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
+ return new ISession() {
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) {
+
+ }
+
+ @Override
+ public void revokeChallenge(int cookie, long challenge) {
+
+ }
+
+ @Override
+ public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal authenticate(int cookie, long operationId) {
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction(int cookie) {
+ return null;
+ }
+
+ @Override
+ public void enumerateEnrollments(int cookie) {
+
+ }
+
+ @Override
+ public void removeEnrollments(int cookie, int[] enrollmentIds) {
+
+ }
+
+ @Override
+ public void getAuthenticatorId(int cookie) {
+
+ }
+
+ @Override
+ public void invalidateAuthenticatorId(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void resetLockout(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
+
+ }
+
+ @Override
+ public void onPointerUp(int pointerId) {
+
+ }
+
+ @Override
+ public void onUiReady() {
+
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return new Binder();
+ }
+ };
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
new file mode 100644
index 000000000000..d5afd0c68ad9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+/**
+ * Test HAL that provides only provides mostly no-ops.
+ */
+class TestSession extends ISession.Stub {
+
+ private static final String TAG = "TestSession";
+
+ @NonNull private final Sensor.HalSessionCallback mHalSessionCallback;
+
+ TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
+ mHalSessionCallback = halSessionCallback;
+ }
+
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) {
+
+ }
+
+ @Override
+ public void revokeChallenge(int cookie, long challenge) {
+
+ }
+
+ @Override
+ public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+ Slog.d(TAG, "enroll");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal authenticate(int cookie, long operationId) {
+ Slog.d(TAG, "authenticate");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction(int cookie) {
+ return null;
+ }
+
+ @Override
+ public void enumerateEnrollments(int cookie) {
+ Slog.d(TAG, "enumerate");
+ }
+
+ @Override
+ public void removeEnrollments(int cookie, int[] enrollmentIds) {
+ Slog.d(TAG, "remove");
+ }
+
+ @Override
+ public void getAuthenticatorId(int cookie) {
+ Slog.d(TAG, "getAuthenticatorId");
+ // Immediately return a value so the framework can continue with subsequent requests.
+ mHalSessionCallback.onAuthenticatorIdRetrieved(0);
+ }
+
+ @Override
+ public void invalidateAuthenticatorId(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void resetLockout(int cookie, HardwareAuthToken hat) {
+
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
+
+ }
+
+ @Override
+ public void onPointerUp(int pointerId) {
+
+ }
+
+ @Override
+ public void onUiReady() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
new file mode 100644
index 000000000000..e0ea99077d51
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import static android.Manifest.permission.TEST_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * A test session implementation for the {@link Fingerprint21} provider. See
+ * {@link android.hardware.biometrics.BiometricTestSession}.
+ */
+public class BiometricTestSessionImpl extends ITestSession.Stub {
+
+ private static final String TAG = "BiometricTestSessionImpl";
+
+ @NonNull private final Context mContext;
+ private final int mSensorId;
+ @NonNull private final Fingerprint21 mFingerprint21;
+ @NonNull private final Fingerprint21.HalResultController mHalResultController;
+ @NonNull private final Set<Integer> mEnrollmentIds;
+ @NonNull private final Random mRandom;
+
+ /**
+ * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
+ * test, since enrollment is a platform-only API. The authentication path is tested through
+ * the public FingerprintManager APIs and does not use this receiver.
+ */
+ private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
+ @Override
+ public void onEnrollResult(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(Fingerprint fp, int userId,
+ boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+
+ }
+
+ @Override
+ public void onError(int error, int vendorCode) {
+
+ }
+
+ @Override
+ public void onRemoved(Fingerprint fp, int remaining) {
+
+ }
+
+ @Override
+ public void onChallengeGenerated(int sensorId, long challenge) {
+
+ }
+ };
+
+ BiometricTestSessionImpl(@NonNull Context context, int sensorId,
+ @NonNull Fingerprint21 fingerprint21,
+ @NonNull Fingerprint21.HalResultController halResultController) {
+ mContext = context;
+ mSensorId = sensorId;
+ mFingerprint21 = fingerprint21;
+ mHalResultController = halResultController;
+ mEnrollmentIds = new HashSet<>();
+ mRandom = new Random();
+ }
+
+ @Override
+ public void setTestHalEnabled(boolean enabled) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mFingerprint21.setTestHalEnabled(enabled);
+ }
+
+ @Override
+ public void startEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
+ mContext.getOpPackageName(), null /* surface */);
+ }
+
+ @Override
+ public void finishEnroll(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ int nextRandomId = mRandom.nextInt();
+ while (mEnrollmentIds.contains(nextRandomId)) {
+ nextRandomId = mRandom.nextInt();
+ }
+
+ mEnrollmentIds.add(nextRandomId);
+ mHalResultController.onEnrollResult(0 /* deviceId */,
+ nextRandomId /* fingerId */, userId, 0);
+ }
+
+ @Override
+ public void acceptAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ // Fake authentication with any of the existing fingers
+ List<Fingerprint> fingerprints = FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId);
+ if (fingerprints.isEmpty()) {
+ Slog.w(TAG, "No fingerprints, returning");
+ return;
+ }
+ final int fid = fingerprints.get(0).getBiometricId();
+ final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
+ mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
+ }
+
+ @Override
+ public void rejectAuthentication(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
+ }
+
+ @Override
+ public void notifyAcquired(int userId, int acquireInfo) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
+ }
+
+ @Override
+ public void notifyError(int userId, int errorCode) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
+ }
+
+ @Override
+ public void cleanupInternalState(int userId) {
+ Utils.checkPermission(mContext, TEST_BIOMETRIC);
+
+ mFingerprint21.scheduleInternalCleanup(mSensorId, userId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index ab4427c5235c..241c911ce9ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -29,6 +29,7 @@ import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -51,8 +52,11 @@ import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
+import com.android.server.biometrics.fingerprint.FingerprintServiceStateProto;
import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.fingerprint.SensorStateProto;
+import com.android.server.biometrics.fingerprint.UserStateProto;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
@@ -91,6 +95,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
private static final String TAG = "Fingerprint21";
private static final int ENROLL_TIMEOUT_SEC = 60;
+ private boolean mTestHalEnabled;
+
final Context mContext;
private final IActivityTaskManager mActivityTaskManager;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
@@ -391,6 +397,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
private synchronized IBiometricsFingerprint getDaemon() {
+ if (mTestHalEnabled) {
+ return new TestHal();
+ }
+
if (mDaemon != null) {
return mDaemon;
}
@@ -693,7 +703,27 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
- public void dumpProto(int sensorId, FileDescriptor fd) {
+ public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) {
+ final long sensorToken = proto.start(FingerprintServiceStateProto.SENSOR_STATES);
+
+ proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
+ proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null);
+
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(SensorStateProto.USER_STATES);
+ proto.write(UserStateProto.USER_ID, userId);
+ proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId).size());
+ proto.end(userToken);
+ }
+
+ proto.end(sensorToken);
+ }
+
+ @Override
+ public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
PerformanceTracker tracker =
PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
@@ -771,4 +801,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
mScheduler.dump(pw);
}
+
+ void setTestHalEnabled(boolean enabled) {
+ mTestHalEnabled = enabled;
+ }
+
+ @NonNull
+ @Override
+ public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) {
+ return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, this,
+ mHalResultController);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
new file mode 100644
index 000000000000..86c0875af48c
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
+import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
+import android.os.RemoteException;
+
+/**
+ * Test HAL that provides only provides no-ops.
+ */
+public class TestHal extends IBiometricsFingerprint.Stub {
+ @Override
+ public boolean isUdfps(int sensorId) {
+ return false;
+ }
+
+ @Override
+ public void onFingerDown(int x, int y, float minor, float major) {
+
+ }
+
+ @Override
+ public void onFingerUp() {
+
+ }
+
+ @Override
+ public long setNotify(IBiometricsFingerprintClientCallback clientCallback) {
+ return 0;
+ }
+
+ @Override
+ public long preEnroll() {
+ return 0;
+ }
+
+ @Override
+ public int enroll(byte[] hat, int gid, int timeoutSec) {
+ return 0;
+ }
+
+ @Override
+ public int postEnroll() {
+ return 0;
+ }
+
+ @Override
+ public long getAuthenticatorId() {
+ return 0;
+ }
+
+ @Override
+ public int cancel() {
+ return 0;
+ }
+
+ @Override
+ public int enumerate() {
+ return 0;
+ }
+
+ @Override
+ public int remove(int gid, int fid) {
+ return 0;
+ }
+
+ @Override
+ public int setActiveGroup(int gid, String storePath) {
+ return 0;
+ }
+
+ @Override
+ public int authenticate(long operationId, int gid) {
+ return 0;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 15f43a0481bd..fbd089c1f0ee 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -52,7 +52,6 @@ public class DataConnectionStats extends BroadcastReceiver {
private SignalStrength mSignalStrength;
private ServiceState mServiceState;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
- private int mNrState = NetworkRegistrationInfo.NR_STATE_NONE;
public DataConnectionStats(Context context, Handler listenerHandler) {
mContext = context;
@@ -100,7 +99,7 @@ public class DataConnectionStats extends BroadcastReceiver {
: regInfo.getAccessNetworkTechnology();
// If the device is in NSA NR connection the networkType will report as LTE.
// For cell dwell rate metrics, this should report NR instead.
- if (mNrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
+ if (regInfo != null && regInfo.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
networkType = TelephonyManager.NETWORK_TYPE_NR;
}
if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
@@ -172,7 +171,6 @@ public class DataConnectionStats extends BroadcastReceiver {
@Override
public void onServiceStateChanged(ServiceState state) {
mServiceState = state;
- mNrState = state.getNrState();
notePhoneDataConnectionState();
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index c686ba4c4b5c..6a4ca8da00d1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -210,7 +210,7 @@ public class SyncManager {
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
- private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = false;
+ private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = true;
private static final int SYNC_OP_STATE_VALID = 0;
// "1" used to include errors 3, 4 and 5 but now it's split up.
@@ -231,7 +231,7 @@ public class SyncManager {
private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
- // TODO: add better locking around mRunningAccounts
+ private final Object mAccountsLock = new Object();
private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
@@ -933,19 +933,21 @@ public class SyncManager {
}
AccountAndUser[] accounts = null;
- if (requestedAccount != null) {
- if (userId != UserHandle.USER_ALL) {
- accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)};
- } else {
- for (AccountAndUser runningAccount : mRunningAccounts) {
- if (requestedAccount.equals(runningAccount.account)) {
- accounts = ArrayUtils.appendElement(AccountAndUser.class,
- accounts, runningAccount);
+ synchronized (mAccountsLock) {
+ if (requestedAccount != null) {
+ if (userId != UserHandle.USER_ALL) {
+ accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)};
+ } else {
+ for (AccountAndUser runningAccount : mRunningAccounts) {
+ if (requestedAccount.equals(runningAccount.account)) {
+ accounts = ArrayUtils.appendElement(AccountAndUser.class,
+ accounts, runningAccount);
+ }
}
}
+ } else {
+ accounts = mRunningAccounts;
}
- } else {
- accounts = mRunningAccounts;
}
if (ArrayUtils.isEmpty(accounts)) {
@@ -3228,40 +3230,43 @@ public class SyncManager {
}
private void updateRunningAccountsH(EndPoint syncTargets) {
- AccountAndUser[] oldAccounts = mRunningAccounts;
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "Accounts list: ");
- for (AccountAndUser acc : mRunningAccounts) {
- Slog.v(TAG, acc.toString());
+ synchronized (mAccountsLock) {
+ AccountAndUser[] oldAccounts = mRunningAccounts;
+ mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, "Accounts list: ");
+ for (AccountAndUser acc : mRunningAccounts) {
+ Slog.v(TAG, acc.toString());
+ }
}
- }
- if (mLogger.enabled()) {
- mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
- }
- removeStaleAccounts();
-
- AccountAndUser[] accounts = mRunningAccounts;
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- if (!containsAccountAndUser(accounts,
- currentSyncContext.mSyncOperation.target.account,
- currentSyncContext.mSyncOperation.target.userId)) {
- Log.d(TAG, "canceling sync since the account is no longer running");
- sendSyncFinishedOrCanceledMessage(currentSyncContext,
- null /* no result since this is a cancel */);
+ if (mLogger.enabled()) {
+ mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
+ }
+ removeStaleAccounts();
+
+ AccountAndUser[] accounts = mRunningAccounts;
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ if (!containsAccountAndUser(accounts,
+ currentSyncContext.mSyncOperation.target.account,
+ currentSyncContext.mSyncOperation.target.userId)) {
+ Log.d(TAG, "canceling sync since the account is no longer running");
+ sendSyncFinishedOrCanceledMessage(currentSyncContext,
+ null /* no result since this is a cancel */);
+ }
}
- }
- if (syncTargets != null) {
- // On account add, check if there are any settings to be restored.
- for (AccountAndUser aau : mRunningAccounts) {
- if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Account " + aau.account
- + " added, checking sync restore data");
+ if (syncTargets != null) {
+ // On account add, check if there are any settings to be restored.
+ for (AccountAndUser aau : mRunningAccounts) {
+ if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Account " + aau.account
+ + " added, checking sync restore data");
+ }
+ AccountSyncSettingsBackupHelper.accountAdded(mContext,
+ syncTargets.userId);
+ break;
}
- AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId);
- break;
}
}
}
@@ -3442,13 +3447,15 @@ public class SyncManager {
final EndPoint target = op.target;
// Drop the sync if the account of this operation no longer exists.
- AccountAndUser[] accounts = mRunningAccounts;
- if (!containsAccountAndUser(accounts, target.account, target.userId)) {
- if (isLoggable) {
- Slog.v(TAG, " Dropping sync operation: account doesn't exist.");
+ synchronized (mAccountsLock) {
+ AccountAndUser[] accounts = mRunningAccounts;
+ if (!containsAccountAndUser(accounts, target.account, target.userId)) {
+ if (isLoggable) {
+ Slog.v(TAG, " Dropping sync operation: account doesn't exist.");
+ }
+ logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist.");
+ return SYNC_OP_STATE_INVALID_NO_ACCOUNT;
}
- logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist.");
- return SYNC_OP_STATE_INVALID_NO_ACCOUNT;
}
// Drop this sync request if it isn't syncable.
state = computeSyncable(target.account, target.userId, target.provider, true);
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index a9e8719890ef..8980de12a31f 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -339,7 +339,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
// This is to manager CEC device separately in case they don't have address.
if (mIsTvDevice) {
- tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
+ localDevice().mService.getHdmiCecNetwork().updateCecSwitchInfo(current.mLogicalAddress,
+ current.mDeviceType,
current.mPhysicalAddress);
}
increaseProcessedDeviceCount();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 96679c3ab767..efe730231d36 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -32,7 +32,6 @@ import android.os.Looper;
import android.os.RemoteException;
import android.stats.hdmi.HdmiStatsEnums;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
@@ -97,7 +96,7 @@ final class HdmiCecController {
private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
@Override
public boolean test(Integer address) {
- return !isAllocatedLocalDeviceAddress(address);
+ return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address);
}
};
@@ -118,9 +117,6 @@ final class HdmiCecController {
private final HdmiControlService mService;
- // Stores the local CEC devices in the system. Device type is used for key.
- private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
-
// Stores recent CEC messages and HDMI Hotplug event history for debugging purpose.
private final ArrayBlockingQueue<Dumpable> mMessageHistory =
new ArrayBlockingQueue<>(MAX_HDMI_MESSAGE_HISTORY);
@@ -173,12 +169,6 @@ final class HdmiCecController {
nativeWrapper.setCallback(new HdmiCecCallback());
}
- @ServiceThreadOnly
- void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
- assertRunOnServiceThread();
- mLocalDevices.put(deviceType, device);
- }
-
/**
* Allocate a new logical address of the given device type. Allocated
* address will be reported through {@link AllocateAddressCallback}.
@@ -269,17 +259,6 @@ final class HdmiCecController {
}
/**
- * Return the locally hosted logical device of a given type.
- *
- * @param deviceType logical device type
- * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
- * otherwise null.
- */
- HdmiCecLocalDevice getLocalDevice(int deviceType) {
- return mLocalDevices.get(deviceType);
- }
-
- /**
* Add a new logical address to the device. Device's HW should be notified
* when a new logical address is assigned to a device, so that it can accept
* a command having available destinations.
@@ -307,18 +286,9 @@ final class HdmiCecController {
@ServiceThreadOnly
void clearLogicalAddress() {
assertRunOnServiceThread();
- for (int i = 0; i < mLocalDevices.size(); ++i) {
- mLocalDevices.valueAt(i).clearAddress();
- }
mNativeWrapperImpl.nativeClearLogicalAddress();
}
- @ServiceThreadOnly
- void clearLocalDevices() {
- assertRunOnServiceThread();
- mLocalDevices.clear();
- }
-
/**
* Return the physical address of the device.
*
@@ -428,17 +398,6 @@ final class HdmiCecController {
runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
}
- /**
- * Return a list of all {@link HdmiCecLocalDevice}s.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- */
- @ServiceThreadOnly
- List<HdmiCecLocalDevice> getLocalDeviceList() {
- assertRunOnServiceThread();
- return HdmiUtils.sparseArrayToList(mLocalDevices);
- }
-
private List<Integer> pickPollCandidates(int pickStrategy) {
int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Predicate<Integer> pickPredicate = null;
@@ -475,17 +434,6 @@ final class HdmiCecController {
}
@ServiceThreadOnly
- private boolean isAllocatedLocalDeviceAddress(int address) {
- assertRunOnServiceThread();
- for (int i = 0; i < mLocalDevices.size(); ++i) {
- if (mLocalDevices.valueAt(i).isAddressOf(address)) {
- return true;
- }
- }
- return false;
- }
-
- @ServiceThreadOnly
private void runDevicePolling(final int sourceAddress,
final List<Integer> candidates, final int retryCount,
final DevicePollingCallback callback, final List<Integer> allocated) {
@@ -578,7 +526,7 @@ final class HdmiCecController {
if (address == Constants.ADDR_BROADCAST) {
return true;
}
- return isAllocatedLocalDeviceAddress(address);
+ return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address);
}
@ServiceThreadOnly
@@ -682,7 +630,7 @@ final class HdmiCecController {
private int incomingMessageDirection(int srcAddress, int dstAddress) {
boolean sourceIsLocal = false;
boolean destinationIsLocal = false;
- for (HdmiCecLocalDevice localDevice : getLocalDeviceList()) {
+ for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) {
int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress();
if (logicalAddress == srcAddress) {
sourceIsLocal = true;
@@ -731,24 +679,9 @@ final class HdmiCecController {
}
void dump(final IndentingPrintWriter pw) {
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
- for (int i = 0; i < mLocalDevices.size(); ++i) {
- pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
- pw.increaseIndent();
- mLocalDevices.valueAt(i).dump(pw);
-
- pw.println("Active Source history:");
- pw.increaseIndent();
- for (Dumpable activeSourceEvent : mLocalDevices.valueAt(i).getActiveSourceHistory()) {
- activeSourceEvent.dump(pw, sdf);
- }
- pw.decreaseIndent();
- pw.decreaseIndent();
- }
-
pw.println("CEC message history:");
pw.increaseIndent();
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Dumpable record : mMessageHistory) {
record.dump(pw, sdf);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 946fb0d00d60..62a67b6243d7 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -471,8 +472,27 @@ abstract class HdmiCecLocalDevice {
return false;
}
+ @CallSuper
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
- return false;
+ // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network
+ // state
+
+ int address = message.getSource();
+
+ // Ignore if [Device Discovery Action] is going on.
+ if (hasAction(DeviceDiscoveryAction.class)) {
+ Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
+ return true;
+ }
+
+ HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address);
+ // If no non-default display name is available for the device, request the devices OSD name.
+ if (cecDeviceInfo.getDisplayName().equals(HdmiUtils.getDefaultDeviceName(address))) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
+ }
+
+ return true;
}
protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
@@ -708,7 +728,7 @@ abstract class HdmiCecLocalDevice {
}
protected boolean handleSetOsdName(HdmiCecMessage message) {
- // The default behavior of <Set Osd Name> is doing nothing.
+ // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state
return true;
}
@@ -724,7 +744,8 @@ abstract class HdmiCecLocalDevice {
}
protected boolean handleReportPowerStatus(HdmiCecMessage message) {
- return false;
+ // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state
+ return true;
}
protected boolean handleTimerStatus(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 29bdd6cb40c3..fe4fd3805994 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -37,7 +37,6 @@ import android.os.SystemProperties;
import android.provider.Settings.Global;
import android.sysprop.HdmiProperties;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -54,15 +53,12 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
-
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
* system.
@@ -104,14 +100,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
@GuardedBy("mLock")
private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>();
- // Copy of mDeviceInfos to guarantee thread-safety.
- @GuardedBy("mLock")
- private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
-
- // Map-like container of all cec devices.
- // device id is used as key of container.
- private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
-
// Message buffer used to buffer selected messages to process later. <Active Source>
// from a source device, for instance, needs to be buffered if the device is not
// discovered yet. The buffered commands are taken out and when they are ready to
@@ -187,135 +175,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
return info != null;
}
- /**
- * Called when a device is newly added or a new device is detected or
- * an existing device is updated.
- *
- * @param info device info of a new device.
- */
- @ServiceThreadOnly
- final void addCecDevice(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- HdmiDeviceInfo old = addDeviceInfo(info);
- if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
- // The addition of the device itself should not be notified.
- // Note that different logical address could still be the same local device.
- return;
- }
- if (old == null) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- } else if (!old.equals(info)) {
- invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- }
- }
-
- /**
- * Called when a device is removed or removal of device is detected.
- *
- * @param address a logical address of a device to be removed
- */
- @ServiceThreadOnly
- final void removeCecDevice(int address) {
- assertRunOnServiceThread();
- HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
- mCecMessageCache.flushMessagesFrom(address);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
-
- /**
- * Called when a device is updated.
- *
- * @param info device info of the updating device.
- */
- @ServiceThreadOnly
- final void updateCecDevice(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- HdmiDeviceInfo old = addDeviceInfo(info);
-
- if (old == null) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- } else if (!old.equals(info)) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
- }
- }
-
- /**
- * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
- * logical address as new device info's.
- *
- * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
- * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
- * that has the same logical address as new one has.
- */
- @ServiceThreadOnly
- @VisibleForTesting
- protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
- assertRunOnServiceThread();
- mService.checkLogicalAddressConflictAndReallocate(deviceInfo.getLogicalAddress());
- HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
- if (oldDeviceInfo != null) {
- removeDeviceInfo(deviceInfo.getId());
- }
- mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
- updateSafeDeviceInfoList();
- return oldDeviceInfo;
- }
-
- /**
- * Remove a device info corresponding to the given {@code logicalAddress}.
- * It returns removed {@link HdmiDeviceInfo} if exists.
- *
- * @param id id of device to be removed
- * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
- */
- @ServiceThreadOnly
- private HdmiDeviceInfo removeDeviceInfo(int id) {
- assertRunOnServiceThread();
- HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
- if (deviceInfo != null) {
- mDeviceInfos.remove(id);
- }
- updateSafeDeviceInfoList();
- return deviceInfo;
- }
-
- /**
- * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
- *
- * @param logicalAddress logical address of the device to be retrieved
- * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
- * Returns null if no logical address matched
- */
- @ServiceThreadOnly
- HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
- assertRunOnServiceThread();
- return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
- }
-
- @ServiceThreadOnly
- private void updateSafeDeviceInfoList() {
- assertRunOnServiceThread();
- List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
- synchronized (mLock) {
- mSafeAllDeviceInfos = copiedDevices;
- }
- }
-
- @GuardedBy("mLock")
- List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- infoList.add(info);
- }
- return infoList;
- }
-
- private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
- mService.invokeDeviceEventListeners(info, status);
- }
-
@Override
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
@@ -342,7 +201,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
}
// Update with TIF on the device removal. TIF callback will update
// mPortIdToTvInputs and mPortIdToTvInputs.
- removeCecDevice(info.getLogicalAddress());
+ mService.getHdmiCecNetwork().removeCecDevice(this, info.getLogicalAddress());
}
}
@@ -399,7 +258,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
boolean lastSystemAudioControlStatus =
SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
- clearDeviceInfoList();
+ mService.getHdmiCecNetwork().clearDeviceList();
launchDeviceDiscovery();
startQueuedActions();
}
@@ -458,7 +317,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
// If the new Active Source is under the current device, check if the device info and the TV
// input is ready to switch to the new Active Source. If not ready, buffer the cec command
// to handle later when the device is ready.
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
if (info == null) {
HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
mDelayedMessageBuffer.add(message);
@@ -474,79 +333,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
@Override
@ServiceThreadOnly
- protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
- assertRunOnServiceThread();
- int path = HdmiUtils.twoBytesToInt(message.getParams());
- int address = message.getSource();
- int type = message.getParams()[2];
-
- // Ignore if [Device Discovery Action] is going on.
- if (hasAction(DeviceDiscoveryAction.class)) {
- Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
- return true;
- }
-
- // Update the device info with TIF, note that the same device info could have added in
- // device discovery and we do not want to override it with default OSD name. Therefore we
- // need the following check to skip redundant device info updating.
- HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
- if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
- addCecDevice(new HdmiDeviceInfo(
- address, path, mService.pathToPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, ""));
- // if we are adding a new device info, send out a give osd name command
- // to update the name of the device in TIF
- mService.sendCecCommand(
- HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
- return true;
- }
-
- Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
- return true;
- }
-
- @Override
- protected boolean handleReportPowerStatus(HdmiCecMessage command) {
- int newStatus = command.getParams()[0] & 0xFF;
- updateDevicePowerStatus(command.getSource(), newStatus);
- return true;
- }
-
- @Override
- @ServiceThreadOnly
- protected boolean handleSetOsdName(HdmiCecMessage message) {
- int source = message.getSource();
- String osdName;
- HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
- // If the device is not in device list, ignore it.
- if (deviceInfo == null) {
- Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
- return true;
- }
- try {
- osdName = new String(message.getParams(), "US-ASCII");
- } catch (UnsupportedEncodingException e) {
- Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
- return true;
- }
-
- if (deviceInfo.getDisplayName() != null
- && deviceInfo.getDisplayName().equals(osdName)) {
- Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
- return true;
- }
-
- Slog.d(TAG, "Updating device OSD name from "
- + deviceInfo.getDisplayName()
- + " to " + osdName);
- updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
- deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
- return true;
- }
-
- @Override
- @ServiceThreadOnly
protected boolean handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement initiate arc handler
@@ -864,14 +650,9 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
!= HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
return true;
}
- boolean isDeviceInCecDeviceList = false;
- for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
- if (info.getPhysicalAddress() == sourcePhysicalAddress) {
- isDeviceInCecDeviceList = true;
- break;
- }
- }
- if (!isDeviceInCecDeviceList) {
+ HdmiDeviceInfo safeDeviceInfoByPath =
+ mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress);
+ if (safeDeviceInfoByPath == null) {
switchInputOnReceivingNewActivePath(sourcePhysicalAddress);
}
}
@@ -1345,24 +1126,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
routeToInputFromPortId(getRoutingPort());
}
- protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
- if (info == null) {
- Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
- return;
- }
-
- if (info.getDevicePowerStatus() == newPowerStatus) {
- return;
- }
-
- HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
- // addDeviceInfo replaces old device info with new one if exists.
- addDeviceInfo(newInfo);
-
- invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
- }
-
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
@@ -1375,27 +1138,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
- addCecDevice(info);
+ mService.getHdmiCecNetwork().addCecDevice(info);
}
}
});
addAndStartAction(action);
}
- // Clear all device info.
- @ServiceThreadOnly
- private void clearDeviceInfoList() {
- assertRunOnServiceThread();
- for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
- if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
- continue;
- }
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
- mDeviceInfos.clear();
- updateSafeDeviceInfoList();
- }
-
@Override
protected void dump(IndentingPrintWriter pw) {
pw.println("HdmiCecLocalDeviceAudioSystem:");
@@ -1409,7 +1158,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
pw.println("mLocalActivePort: " + getLocalActivePort());
HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs);
HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo);
- HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
pw.decreaseIndent();
super.dump(pw);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 0325ad929849..93cdca2d0c83 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -42,9 +42,7 @@ import android.media.AudioSystem;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager.TvInputCallback;
import android.provider.Settings.Global;
-import android.util.ArraySet;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
@@ -53,13 +51,8 @@ import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
/**
@@ -95,37 +88,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@GuardedBy("mLock")
private boolean mSystemAudioMute = false;
- // Copy of mDeviceInfos to guarantee thread-safety.
- @GuardedBy("mLock")
- private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
- // All external cec input(source) devices. Does not include system audio device.
- @GuardedBy("mLock")
- private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
-
- // Map-like container of all cec devices including local ones.
- // device id is used as key of container.
- // This is not thread-safe. For external purpose use mSafeDeviceInfos.
- private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
-
// If true, TV going to standby mode puts other devices also to standby.
private boolean mAutoDeviceOff;
// If true, TV wakes itself up when receiving <Text/Image View On>.
private boolean mAutoWakeup;
- // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
- private List<Integer> mLocalDeviceAddresses;
-
private final HdmiCecStandbyModeHandler mStandbyHandler;
// If true, do not do routing control/send active source for internal source.
// Set to true when the device was woken up by <Text/Image View On>.
private boolean mSkipRoutingControl;
- // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
- // other CEC devices since they might not have logical address.
- private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
-
// Message buffer used to buffer selected messages to process later. <Active Source>
// from a source device, for instance, needs to be buffered if the device is not
// discovered yet. The buffered commands are taken out and when they are ready to
@@ -205,12 +179,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
mAddress, mService.getPhysicalAddress(), mDeviceType));
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
mAddress, mService.getVendorId()));
- mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too.
+ mService.getHdmiCecNetwork().addCecSwitch(
+ mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too.
mTvInputs.clear();
mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
reason != HdmiControlService.INITIATED_BY_BOOT_UP);
- mLocalDeviceAddresses = initLocalDeviceAddresses();
resetSelectRequestBuffer();
launchDeviceDiscovery();
startQueuedActions();
@@ -220,17 +194,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
@ServiceThreadOnly
- private List<Integer> initLocalDeviceAddresses() {
- assertRunOnServiceThread();
- List<Integer> addresses = new ArrayList<>();
- for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
- addresses.add(device.getDeviceInfo().getLogicalAddress());
- }
- return Collections.unmodifiableList(addresses);
- }
-
-
- @ServiceThreadOnly
public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
assertRunOnServiceThread();
mSelectRequestBuffer = requestBuffer;
@@ -272,7 +235,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
void deviceSelect(int id, IHdmiControlCallback callback) {
assertRunOnServiceThread();
- HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
+ HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
if (targetDevice == null) {
invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
return;
@@ -335,7 +298,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
setActiveSource(newActive, caller);
int logicalAddress = newActive.logicalAddress;
- if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
+ if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null
+ && logicalAddress != mAddress) {
if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
setPrevPortId(getActivePortId());
}
@@ -374,7 +338,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
// Show OSD port change banner
if (notifyInputChange) {
ActiveSource activeSource = getActiveSource();
- HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(
+ activeSource.logicalAddress);
if (info == null) {
info = mService.getDeviceInfoByPort(getActivePortId());
if (info == null) {
@@ -442,7 +407,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
if (getActiveSource().isValid()) {
return getActiveSource().logicalAddress;
}
- HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath());
if (info != null) {
return info.getLogicalAddress();
}
@@ -455,7 +420,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
assertRunOnServiceThread();
int logicalAddress = message.getSource();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
if (info == null) {
if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
@@ -463,7 +428,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
} else if (isInputReady(info.getId())
|| info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
- updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
+ mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
+ HdmiControlManager.POWER_STATUS_ON);
ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
} else {
@@ -490,7 +456,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
if (portId != Constants.INVALID_PORT_ID) {
// TODO: Do this only if TV is not showing multiview like PIP/PAP.
- HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
+ HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo(
+ message.getSource());
if (inactiveSource == null) {
return true;
}
@@ -546,42 +513,20 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
@Override
- @ServiceThreadOnly
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
- assertRunOnServiceThread();
+ super.handleReportPhysicalAddress(message);
int path = HdmiUtils.twoBytesToInt(message.getParams());
int address = message.getSource();
int type = message.getParams()[2];
- if (updateCecSwitchInfo(address, type, path)) return true;
-
- // Ignore if [Device Discovery Action] is going on.
- if (hasAction(DeviceDiscoveryAction.class)) {
- Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
- return true;
- }
-
- if (!isInDeviceList(address, path)) {
+ if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
handleNewDeviceAtTheTailOfActivePath(path);
}
-
- // Add the device ahead with default information to handle <Active Source>
- // promptly, rather than waiting till the new device action is finished.
- HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
- addCecDevice(deviceInfo);
startNewDeviceAction(ActiveSource.of(address, path), type);
return true;
}
@Override
- protected boolean handleReportPowerStatus(HdmiCecMessage command) {
- int newStatus = command.getParams()[0] & 0xFF;
- updateDevicePowerStatus(command.getSource(), newStatus);
- return true;
- }
-
- @Override
protected boolean handleTimerStatus(HdmiCecMessage message) {
// Do nothing.
return true;
@@ -593,19 +538,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return true;
}
- boolean updateCecSwitchInfo(int address, int type, int path) {
- if (address == Constants.ADDR_UNREGISTERED
- && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
- mCecSwitches.add(path);
- updateSafeDeviceInfoList();
- return true; // Pure switch does not need further processing. Return here.
- }
- if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
- mCecSwitches.add(path);
- }
- return false;
- }
-
void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
// If there is new device action which has the same logical address and path
@@ -719,35 +651,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return handleTextViewOn(message);
}
- @Override
- @ServiceThreadOnly
- protected boolean handleSetOsdName(HdmiCecMessage message) {
- int source = message.getSource();
- HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
- // If the device is not in device list, ignore it.
- if (deviceInfo == null) {
- Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
- return true;
- }
- String osdName = null;
- try {
- osdName = new String(message.getParams(), "US-ASCII");
- } catch (UnsupportedEncodingException e) {
- Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
- return true;
- }
-
- if (deviceInfo.getDisplayName().equals(osdName)) {
- Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
- return true;
- }
-
- addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
- deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
- deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
- return true;
- }
-
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
@@ -757,14 +660,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
- addCecDevice(info);
+ mService.getHdmiCecNetwork().addCecDevice(info);
}
// Since we removed all devices when it's start and
// device discovery action does not poll local devices,
// we should put device info of local device manually here
for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
- addCecDevice(device.getDeviceInfo());
+ mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
}
mSelectRequestBuffer.process();
@@ -798,11 +701,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
private void clearDeviceInfoList() {
assertRunOnServiceThread();
- for (HdmiDeviceInfo info : mSafeExternalInputs) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
- mDeviceInfos.clear();
- updateSafeDeviceInfoList();
+ mService.getHdmiCecNetwork().clearDeviceList();
}
@ServiceThreadOnly
@@ -1224,170 +1123,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
&& getAvrDeviceInfo() != null;
}
- /**
- * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
- * logical address as new device info's.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- *
- * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
- * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
- * that has the same logical address as new one has.
- */
- @ServiceThreadOnly
- private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
- assertRunOnServiceThread();
- HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
- if (oldDeviceInfo != null) {
- removeDeviceInfo(deviceInfo.getId());
- }
- mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
- updateSafeDeviceInfoList();
- return oldDeviceInfo;
- }
-
- /**
- * Remove a device info corresponding to the given {@code logicalAddress}.
- * It returns removed {@link HdmiDeviceInfo} if exists.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- *
- * @param id id of device to be removed
- * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
- */
- @ServiceThreadOnly
- private HdmiDeviceInfo removeDeviceInfo(int id) {
- assertRunOnServiceThread();
- HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
- if (deviceInfo != null) {
- mDeviceInfos.remove(id);
- }
- updateSafeDeviceInfoList();
- return deviceInfo;
- }
-
- /**
- * Return a list of all {@link HdmiDeviceInfo}.
- *
- * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
- * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
- * does not include local device.
- */
- @ServiceThreadOnly
- List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
- assertRunOnServiceThread();
- if (includeLocalDevice) {
- return HdmiUtils.sparseArrayToList(mDeviceInfos);
- } else {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (int i = 0; i < mDeviceInfos.size(); ++i) {
- HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
- if (!isLocalDeviceAddress(info.getLogicalAddress())) {
- infoList.add(info);
- }
- }
- return infoList;
- }
- }
-
- /**
- * Return external input devices.
- */
- @GuardedBy("mLock")
- List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
- return mSafeExternalInputs;
- }
-
- @ServiceThreadOnly
- private void updateSafeDeviceInfoList() {
- assertRunOnServiceThread();
- List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
- List<HdmiDeviceInfo> externalInputs = getInputDevices();
- synchronized (mLock) {
- mSafeAllDeviceInfos = copiedDevices;
- mSafeExternalInputs = externalInputs;
- }
- }
-
- /**
- * Return a list of external cec input (source) devices.
- *
- * <p>Note that this effectively excludes non-source devices like system audio,
- * secondary TV.
- */
- private List<HdmiDeviceInfo> getInputDevices() {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (int i = 0; i < mDeviceInfos.size(); ++i) {
- HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
- if (isLocalDeviceAddress(info.getLogicalAddress())) {
- continue;
- }
- if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
- infoList.add(info);
- }
- }
- return infoList;
- }
-
- // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
- // Returns true if the policy is set to true, and the device to check does not have
- // a parent CEC device (which should be the CEC-enabled switch) in the list.
- private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
- return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
- && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
- }
-
- private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
- for (int switchPath : switches) {
- if (isParentPath(switchPath, path)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isParentPath(int parentPath, int childPath) {
- // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
- // If child's last non-zero nibble is removed, the result equals to the parent.
- for (int i = 0; i <= 12; i += 4) {
- int nibble = (childPath >> i) & 0xF;
- if (nibble != 0) {
- int parentNibble = (parentPath >> i) & 0xF;
- return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
- }
- }
- return false;
- }
-
- private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
- if (!hideDevicesBehindLegacySwitch(info)) {
- mService.invokeDeviceEventListeners(info, status);
- }
- }
-
- private boolean isLocalDeviceAddress(int address) {
- return mLocalDeviceAddresses.contains(address);
- }
-
@ServiceThreadOnly
HdmiDeviceInfo getAvrDeviceInfo() {
assertRunOnServiceThread();
- return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
- }
-
- /**
- * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
- *
- * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
- *
- * @param logicalAddress logical address of the device to be retrieved
- * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
- * Returns null if no logical address matched
- */
- @ServiceThreadOnly
- HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
- assertRunOnServiceThread();
- return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
+ return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
}
boolean hasSystemAudioDevice() {
@@ -1395,74 +1134,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
HdmiDeviceInfo getSafeAvrDeviceInfo() {
- return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
- }
-
- /**
- * Thread safe version of {@link #getCecDeviceInfo(int)}.
- *
- * @param logicalAddress logical address to be retrieved
- * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
- * Returns null if no logical address matched
- */
- HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
- synchronized (mLock) {
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
- return info;
- }
- }
- return null;
- }
- }
-
- @GuardedBy("mLock")
- List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
- ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- if (isLocalDeviceAddress(info.getLogicalAddress())) {
- continue;
- }
- infoList.add(info);
- }
- return infoList;
- }
-
- /**
- * Called when a device is newly added or a new device is detected or
- * existing device is updated.
- *
- * @param info device info of a new device.
- */
- @ServiceThreadOnly
- final void addCecDevice(HdmiDeviceInfo info) {
- assertRunOnServiceThread();
- HdmiDeviceInfo old = addDeviceInfo(info);
- if (info.getLogicalAddress() == mAddress) {
- // The addition of TV device itself should not be notified.
- return;
- }
- if (old == null) {
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- } else if (!old.equals(info)) {
- invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
- }
+ return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
}
- /**
- * Called when a device is removed or removal of device is detected.
- *
- * @param address a logical address of a device to be removed
- */
- @ServiceThreadOnly
- final void removeCecDevice(int address) {
- assertRunOnServiceThread();
- HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
- mCecMessageCache.flushMessagesFrom(address);
- invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
- }
@ServiceThreadOnly
void handleRemoveActiveRoutingPath(int path) {
@@ -1501,72 +1175,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
}
- /**
- * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
- * the given routing path. CEC devices use routing path for its physical address to
- * describe the hierarchy of the devices in the network.
- *
- * @param path routing path or physical address
- * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
- */
- @ServiceThreadOnly
- final HdmiDeviceInfo getDeviceInfoByPath(int path) {
- assertRunOnServiceThread();
- for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
- if (info.getPhysicalAddress() == path) {
- return info;
- }
- }
- return null;
- }
-
- /**
- * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
- * the given routing path. This is the version accessible safely from threads
- * other than service thread.
- *
- * @param path routing path or physical address
- * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
- */
- HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
- synchronized (mLock) {
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- if (info.getPhysicalAddress() == path) {
- return info;
- }
- }
- return null;
- }
- }
-
- /**
- * Whether a device of the specified physical address and logical address exists
- * in a device info list. However, both are minimal condition and it could
- * be different device from the original one.
- *
- * @param logicalAddress logical address of a device to be searched
- * @param physicalAddress physical address of a device to be searched
- * @return true if exist; otherwise false
- */
- @ServiceThreadOnly
- boolean isInDeviceList(int logicalAddress, int physicalAddress) {
- assertRunOnServiceThread();
- HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
- if (device == null) {
- return false;
- }
- return device.getPhysicalAddress() == physicalAddress;
- }
-
@Override
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
-
- if (!connected) {
- removeCecSwitches(portId);
- }
-
// Turning System Audio Mode off when the AVR is unlugged or standby.
// When the device is not unplugged but reawaken from standby, we check if the System
// Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
@@ -1588,16 +1200,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
}
- private void removeCecSwitches(int portId) {
- Iterator<Integer> it = mCecSwitches.iterator();
- while (!it.hasNext()) {
- int path = it.next();
- if (pathToPortId(path) == portId) {
- it.remove();
- }
- }
- }
-
@Override
@ServiceThreadOnly
void setAutoDeviceOff(boolean enabled) {
@@ -1765,7 +1367,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
private boolean checkRecorder(int recorderAddress) {
- HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
+ HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress);
return (device != null)
&& (HdmiUtils.getTypeFromAddress(recorderAddress)
== HdmiDeviceInfo.DEVICE_RECORDER);
@@ -1871,24 +1473,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
});
}
- void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
- HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
- if (info == null) {
- Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
- return;
- }
-
- if (info.getDevicePowerStatus() == newPowerStatus) {
- return;
- }
-
- HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
- // addDeviceInfo replaces old device info with new one if exists.
- addDeviceInfo(newInfo);
-
- invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
- }
-
@Override
protected boolean handleMenuStatus(HdmiCecMessage message) {
// Do nothing and just return true not to prevent from responding <Feature Abort>.
@@ -1897,7 +1481,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@Override
protected void sendStandby(int deviceId) {
- HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
+ HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId);
if (targetDevice == null) {
return;
}
@@ -1934,11 +1518,5 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
pw.println("mAutoWakeup: " + mAutoWakeup);
pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
pw.println("mPrevPortId: " + mPrevPortId);
- pw.println("CEC devices:");
- pw.increaseIndent();
- for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
- pw.println(info);
- }
- pw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
new file mode 100644
index 000000000000..5d75a63d0c8d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -0,0 +1,846 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import android.annotation.Nullable;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * Holds information about the current state of the HDMI CEC network. It is the sole source of
+ * truth for device information in the CEC network.
+ *
+ * This information includes:
+ * - All local devices
+ * - All HDMI ports, their capabilities and status
+ * - All devices connected to the CEC bus
+ *
+ * This class receives all incoming CEC messages and passively listens to device updates to fill
+ * out the above information.
+ * This class should not take any active action in sending CEC messages.
+ *
+ * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD
+ * names, power states can be outdated.
+ */
+class HdmiCecNetwork {
+ private static final String TAG = "HdmiCecNetwork";
+
+ protected final Object mLock;
+ private final HdmiControlService mHdmiControlService;
+ private final HdmiCecController mHdmiCecController;
+ private final HdmiMhlControllerStub mHdmiMhlController;
+ private final Handler mHandler;
+ // Stores the local CEC devices in the system. Device type is used for key.
+ private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
+
+ // Map-like container of all cec devices including local ones.
+ // device id is used as key of container.
+ // This is not thread-safe. For external purpose use mSafeDeviceInfos.
+ private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
+ // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
+ // other CEC devices since they might not have logical address.
+ private final ArraySet<Integer> mCecSwitches = new ArraySet<>();
+ // Copy of mDeviceInfos to guarantee thread-safety.
+ @GuardedBy("mLock")
+ private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
+ // All external cec input(source) devices. Does not include system audio device.
+ @GuardedBy("mLock")
+ private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
+ // HDMI port information. Stored in the unmodifiable list to keep the static information
+ // from being modified.
+ @GuardedBy("mLock")
+ private List<HdmiPortInfo> mPortInfo = Collections.emptyList();
+
+ // Map from path(physical address) to port ID.
+ private UnmodifiableSparseIntArray mPortIdMap;
+
+ // Map from port ID to HdmiPortInfo.
+ private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
+
+ // Map from port ID to HdmiDeviceInfo.
+ private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
+
+ HdmiCecNetwork(HdmiControlService hdmiControlService,
+ HdmiCecController hdmiCecController,
+ HdmiMhlControllerStub hdmiMhlController) {
+ mHdmiControlService = hdmiControlService;
+ mHdmiCecController = hdmiCecController;
+ mHdmiMhlController = hdmiMhlController;
+ mHandler = new Handler(mHdmiControlService.getServiceLooper());
+ mLock = mHdmiControlService.getServiceLock();
+ }
+
+ private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
+ for (int switchPath : switches) {
+ if (isParentPath(switchPath, path)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isParentPath(int parentPath, int childPath) {
+ // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
+ // If child's last non-zero nibble is removed, the result equals to the parent.
+ for (int i = 0; i <= 12; i += 4) {
+ int nibble = (childPath >> i) & 0xF;
+ if (nibble != 0) {
+ int parentNibble = (parentPath >> i) & 0xF;
+ return parentNibble == 0 && (childPath >> i + 4) == (parentPath >> i + 4);
+ }
+ }
+ return false;
+ }
+
+ public void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
+ mLocalDevices.put(deviceType, device);
+ }
+
+ /**
+ * Return the locally hosted logical device of a given type.
+ *
+ * @param deviceType logical device type
+ * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
+ * otherwise null.
+ */
+ HdmiCecLocalDevice getLocalDevice(int deviceType) {
+ return mLocalDevices.get(deviceType);
+ }
+
+ /**
+ * Return a list of all {@link HdmiCecLocalDevice}s.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ @ServiceThreadOnly
+ List<HdmiCecLocalDevice> getLocalDeviceList() {
+ assertRunOnServiceThread();
+ return HdmiUtils.sparseArrayToList(mLocalDevices);
+ }
+
+ @ServiceThreadOnly
+ boolean isAllocatedLocalDeviceAddress(int address) {
+ assertRunOnServiceThread();
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ if (mLocalDevices.valueAt(i).isAddressOf(address)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Clear all logical addresses registered in the device.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ @ServiceThreadOnly
+ void clearLogicalAddress() {
+ assertRunOnServiceThread();
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ mLocalDevices.valueAt(i).clearAddress();
+ }
+ }
+
+ @ServiceThreadOnly
+ void clearLocalDevices() {
+ assertRunOnServiceThread();
+ mLocalDevices.clear();
+ }
+
+ public HdmiDeviceInfo getDeviceInfo(int id) {
+ return mDeviceInfos.get(id);
+ }
+
+ /**
+ * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
+ * logical address as new device info's.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
+ * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
+ * that has the same logical address as new one has.
+ */
+ @ServiceThreadOnly
+ private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
+ mHdmiControlService.checkLogicalAddressConflictAndReallocate(
+ deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress());
+ if (oldDeviceInfo != null) {
+ removeDeviceInfo(deviceInfo.getId());
+ }
+ mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
+ updateSafeDeviceInfoList();
+ return oldDeviceInfo;
+ }
+
+ /**
+ * Remove a device info corresponding to the given {@code logicalAddress}.
+ * It returns removed {@link HdmiDeviceInfo} if exists.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param id id of device to be removed
+ * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
+ */
+ @ServiceThreadOnly
+ private HdmiDeviceInfo removeDeviceInfo(int id) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
+ if (deviceInfo != null) {
+ mDeviceInfos.remove(id);
+ }
+ updateSafeDeviceInfoList();
+ return deviceInfo;
+ }
+
+ /**
+ * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
+ *
+ * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
+ *
+ * @param logicalAddress logical address of the device to be retrieved
+ * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
+ * Returns null if no logical address matched
+ */
+ @ServiceThreadOnly
+ @Nullable
+ HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
+ assertRunOnServiceThread();
+ return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
+ }
+
+ /**
+ * Called when a device is newly added or a new device is detected or
+ * existing device is updated.
+ *
+ * @param info device info of a new device.
+ */
+ @ServiceThreadOnly
+ final void addCecDevice(HdmiDeviceInfo info) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo old = addDeviceInfo(info);
+ if (isLocalDeviceAddress(info.getLogicalAddress())) {
+ // The addition of a local device should not notify listeners
+ return;
+ }
+ if (old == null) {
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ } else if (!old.equals(info)) {
+ invokeDeviceEventListener(old,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+ }
+
+ private void invokeDeviceEventListener(HdmiDeviceInfo info, int event) {
+ if (!hideDevicesBehindLegacySwitch(info)) {
+ mHdmiControlService.invokeDeviceEventListeners(info, event);
+ }
+ }
+
+ /**
+ * Called when a device is updated.
+ *
+ * @param info device info of the updating device.
+ */
+ @ServiceThreadOnly
+ final void updateCecDevice(HdmiDeviceInfo info) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo old = addDeviceInfo(info);
+
+ if (old == null) {
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ } else if (!old.equals(info)) {
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+ }
+
+ @ServiceThreadOnly
+ private void updateSafeDeviceInfoList() {
+ assertRunOnServiceThread();
+ List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
+ List<HdmiDeviceInfo> externalInputs = getInputDevices();
+ mSafeAllDeviceInfos = copiedDevices;
+ mSafeExternalInputs = externalInputs;
+ }
+
+ /**
+ * Return a list of all {@link HdmiDeviceInfo}.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
+ * does not include local device.
+ */
+ @ServiceThreadOnly
+ List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
+ assertRunOnServiceThread();
+ if (includeLocalDevice) {
+ return HdmiUtils.sparseArrayToList(mDeviceInfos);
+ } else {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); ++i) {
+ HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
+ if (!isLocalDeviceAddress(info.getLogicalAddress())) {
+ infoList.add(info);
+ }
+ }
+ return infoList;
+ }
+ }
+
+ /**
+ * Return external input devices.
+ */
+ @GuardedBy("mLock")
+ List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
+ return mSafeExternalInputs;
+ }
+
+ /**
+ * Return a list of external cec input (source) devices.
+ *
+ * <p>Note that this effectively excludes non-source devices like system audio,
+ * secondary TV.
+ */
+ private List<HdmiDeviceInfo> getInputDevices() {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); ++i) {
+ HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
+ if (isLocalDeviceAddress(info.getLogicalAddress())) {
+ continue;
+ }
+ if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
+ infoList.add(info);
+ }
+ }
+ return infoList;
+ }
+
+ // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
+ // This only applies to TV devices.
+ // Returns true if the policy is set to true, and the device to check does not have
+ // a parent CEC device (which should be the CEC-enabled switch) in the list.
+ private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
+ return isLocalDeviceAddress(Constants.ADDR_TV)
+ && HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
+ && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches());
+ }
+
+ /**
+ * Called when a device is removed or removal of device is detected.
+ *
+ * @param address a logical address of a device to be removed
+ */
+ @ServiceThreadOnly
+ final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
+
+ localDevice.mCecMessageCache.flushMessagesFrom(address);
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+
+ public void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+ HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ if (info == null) {
+ Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
+ return;
+ }
+
+ if (info.getDevicePowerStatus() == newPowerStatus) {
+ return;
+ }
+
+ updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus));
+ }
+
+ /**
+ * Whether a device of the specified physical address is connected to ARC enabled port.
+ */
+ boolean isConnectedToArcPort(int physicalAddress) {
+ int portId = physicalAddressToPortId(physicalAddress);
+ if (portId != Constants.INVALID_PORT_ID) {
+ return mPortInfoMap.get(portId).isArcSupported();
+ }
+ return false;
+ }
+
+
+ // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
+ // keep them in one place.
+ @ServiceThreadOnly
+ @VisibleForTesting
+ public void initPortInfo() {
+ assertRunOnServiceThread();
+ HdmiPortInfo[] cecPortInfo = null;
+ // CEC HAL provides majority of the info while MHL does only MHL support flag for
+ // each port. Return empty array if CEC HAL didn't provide the info.
+ if (mHdmiCecController != null) {
+ cecPortInfo = mHdmiCecController.getPortInfos();
+ }
+ if (cecPortInfo == null) {
+ return;
+ }
+
+ SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
+ SparseIntArray portIdMap = new SparseIntArray();
+ SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
+ for (HdmiPortInfo info : cecPortInfo) {
+ portIdMap.put(info.getAddress(), info.getId());
+ portInfoMap.put(info.getId(), info);
+ portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
+ }
+ mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
+ mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
+ mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
+
+ if (mHdmiMhlController == null) {
+ return;
+ }
+ HdmiPortInfo[] mhlPortInfo = mHdmiMhlController.getPortInfos();
+ ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
+ for (HdmiPortInfo info : mhlPortInfo) {
+ if (info.isMhlSupported()) {
+ mhlSupportedPorts.add(info.getId());
+ }
+ }
+
+ // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
+ // cec port info if we do not have have port that supports MHL.
+ if (mhlSupportedPorts.isEmpty()) {
+ setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
+ return;
+ }
+ ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
+ for (HdmiPortInfo info : cecPortInfo) {
+ if (mhlSupportedPorts.contains(info.getId())) {
+ result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
+ info.isCecSupported(), true, info.isArcSupported()));
+ } else {
+ result.add(info);
+ }
+ }
+ setPortInfo(Collections.unmodifiableList(result));
+ }
+
+ HdmiDeviceInfo getDeviceForPortId(int portId) {
+ return mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
+ }
+
+ /**
+ * Whether a device of the specified physical address and logical address exists
+ * in a device info list. However, both are minimal condition and it could
+ * be different device from the original one.
+ *
+ * @param logicalAddress logical address of a device to be searched
+ * @param physicalAddress physical address of a device to be searched
+ * @return true if exist; otherwise false
+ */
+ @ServiceThreadOnly
+ boolean isInDeviceList(int logicalAddress, int physicalAddress) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
+ if (device == null) {
+ return false;
+ }
+ return device.getPhysicalAddress() == physicalAddress;
+ }
+
+ /**
+ * Passively listen to incoming CEC messages.
+ *
+ * This shall not result in any CEC messages being sent.
+ */
+ @ServiceThreadOnly
+ public void handleCecMessage(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Add device by logical address if it's not already known
+ int sourceAddress = message.getSource();
+ if (getCecDeviceInfo(sourceAddress) == null) {
+ HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress,
+ HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID,
+ HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID,
+ HdmiUtils.getDefaultDeviceName(sourceAddress));
+ addCecDevice(newDevice);
+ }
+
+ switch (message.getOpcode()) {
+ case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ handleReportPhysicalAddress(message);
+ break;
+ case Constants.MESSAGE_REPORT_POWER_STATUS:
+ handleReportPowerStatus(message);
+ break;
+ case Constants.MESSAGE_SET_OSD_NAME:
+ handleSetOsdName(message);
+ break;
+ case Constants.MESSAGE_DEVICE_VENDOR_ID:
+ handleDeviceVendorId(message);
+ break;
+
+ }
+ }
+
+ @ServiceThreadOnly
+ private void handleReportPhysicalAddress(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+ int type = message.getParams()[2];
+
+ if (updateCecSwitchInfo(logicalAddress, type, physicalAddress)) return;
+
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress);
+ if (deviceInfo == null) {
+ Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message);
+ } else {
+ HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ physicalAddress,
+ physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(),
+ deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus());
+ updateCecDevice(updatedDeviceInfo);
+ }
+ }
+
+ @ServiceThreadOnly
+ private void handleReportPowerStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Update power status of device
+ int newStatus = message.getParams()[0] & 0xFF;
+ updateDevicePowerStatus(message.getSource(), newStatus);
+ }
+
+ @ServiceThreadOnly
+ private void handleSetOsdName(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ String osdName;
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress);
+ // If the device is not in device list, ignore it.
+ if (deviceInfo == null) {
+ Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
+ return;
+ }
+ try {
+ osdName = new String(message.getParams(), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
+ return;
+ }
+
+ if (deviceInfo.getDisplayName() != null
+ && deviceInfo.getDisplayName().equals(osdName)) {
+ Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
+ return;
+ }
+
+ Slog.d(TAG, "Updating device OSD name from "
+ + deviceInfo.getDisplayName()
+ + " to " + osdName);
+ updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
+ deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName,
+ deviceInfo.getDevicePowerStatus()));
+ }
+
+ @ServiceThreadOnly
+ private void handleDeviceVendorId(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int logicalAddress = message.getSource();
+ int vendorId = HdmiUtils.threeBytesToInt(message.getParams());
+
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress);
+ if (deviceInfo == null) {
+ Slog.i(TAG, "Unknown source device info for <Device Vendor ID> " + message);
+ } else {
+ HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ deviceInfo.getPhysicalAddress(),
+ deviceInfo.getPortId(), deviceInfo.getDeviceType(), vendorId,
+ deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus());
+ updateCecDevice(updatedDeviceInfo);
+ }
+ }
+
+ void addCecSwitch(int physicalAddress) {
+ mCecSwitches.add(physicalAddress);
+ }
+
+ public ArraySet<Integer> getCecSwitches() {
+ return mCecSwitches;
+ }
+
+ void removeDevicesConnectedToPort(int portId) {
+ Iterator<Integer> it = mCecSwitches.iterator();
+ while (it.hasNext()) {
+ int path = it.next();
+ int devicePortId = physicalAddressToPortId(path);
+ if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) {
+ it.remove();
+ }
+ }
+ List<Integer> toRemove = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); i++) {
+ int key = mDeviceInfos.keyAt(i);
+ int physicalAddress = mDeviceInfos.get(key).getPhysicalAddress();
+ int devicePortId = physicalAddressToPortId(physicalAddress);
+ if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) {
+ toRemove.add(key);
+ }
+ }
+ for (Integer key : toRemove) {
+ removeDeviceInfo(key);
+ }
+ }
+
+ boolean updateCecSwitchInfo(int address, int type, int path) {
+ if (address == Constants.ADDR_UNREGISTERED
+ && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
+ mCecSwitches.add(path);
+ updateSafeDeviceInfoList();
+ return true; // Pure switch does not need further processing. Return here.
+ }
+ if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
+ mCecSwitches.add(path);
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
+ ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ if (isLocalDeviceAddress(info.getLogicalAddress())) {
+ continue;
+ }
+ infoList.add(info);
+ }
+ return infoList;
+ }
+
+ /**
+ * Thread safe version of {@link #getCecDeviceInfo(int)}.
+ *
+ * @param logicalAddress logical address to be retrieved
+ * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
+ * Returns null if no logical address matched
+ */
+ HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
+ *
+ *
+ *
+ * qq * the given routing path. CEC devices use routing path for its physical address to
+ * describe the hierarchy of the devices in the network.
+ *
+ * @param path routing path or physical address
+ * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
+ */
+ @ServiceThreadOnly
+ final HdmiDeviceInfo getDeviceInfoByPath(int path) {
+ assertRunOnServiceThread();
+ for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
+ if (info.getPhysicalAddress() == path) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
+ * the given routing path. This is the version accessible safely from threads
+ * other than service thread.
+ *
+ * @param path routing path or physical address
+ * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
+ */
+ HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
+ for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+ if (info.getPhysicalAddress() == path) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public int getPhysicalAddress() {
+ return mHdmiCecController.getPhysicalAddress();
+ }
+
+ @ServiceThreadOnly
+ public void clear() {
+ assertRunOnServiceThread();
+ initPortInfo();
+ clearDeviceList();
+ clearLocalDevices();
+ }
+
+ @ServiceThreadOnly
+ public void clearDeviceList() {
+ assertRunOnServiceThread();
+ for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
+ if (info.getPhysicalAddress() == getPhysicalAddress()) {
+ continue;
+ }
+ invokeDeviceEventListener(info,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+ mDeviceInfos.clear();
+ updateSafeDeviceInfoList();
+ }
+
+ /**
+ * Returns HDMI port information for the given port id.
+ *
+ * @param portId HDMI port id
+ * @return {@link HdmiPortInfo} for the given port
+ */
+ HdmiPortInfo getPortInfo(int portId) {
+ return mPortInfoMap.get(portId, null);
+ }
+
+ /**
+ * Returns the routing path (physical address) of the HDMI port for the given
+ * port id.
+ */
+ int portIdToPath(int portId) {
+ HdmiPortInfo portInfo = getPortInfo(portId);
+ if (portInfo == null) {
+ Slog.e(TAG, "Cannot find the port info: " + portId);
+ return Constants.INVALID_PHYSICAL_ADDRESS;
+ }
+ return portInfo.getAddress();
+ }
+
+ /**
+ * Returns the id of HDMI port located at the current device that runs this method.
+ *
+ * For TV with physical address 0x0000, target device 0x1120, we want port physical address
+ * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
+ * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
+ *
+ * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
+ *
+ * @param path the target device's physical address.
+ * @return the id of the port that the target device eventually connects to
+ * on the current device.
+ */
+ int physicalAddressToPortId(int path) {
+ int mask = 0xF000;
+ int finalMask = 0xF000;
+ int physicalAddress;
+ physicalAddress = getPhysicalAddress();
+ int maskedAddress = physicalAddress;
+
+ while (maskedAddress != 0) {
+ maskedAddress = physicalAddress & mask;
+ finalMask |= mask;
+ mask >>= 4;
+ }
+
+ int portAddress = path & finalMask;
+ return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
+ }
+
+ List<HdmiPortInfo> getPortInfo() {
+ return mPortInfo;
+ }
+
+ void setPortInfo(List<HdmiPortInfo> portInfo) {
+ mPortInfo = portInfo;
+ }
+
+ private boolean isLocalDeviceAddress(int address) {
+ for (int i = 0; i < mLocalDevices.size(); i++) {
+ int key = mLocalDevices.keyAt(i);
+ if (mLocalDevices.get(key).mAddress == address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void assertRunOnServiceThread() {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ throw new IllegalStateException("Should run on service thread.");
+ }
+ }
+
+ protected void dump(IndentingPrintWriter pw) {
+ pw.println("HDMI CEC Network");
+ pw.increaseIndent();
+ HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
+ pw.increaseIndent();
+ mLocalDevices.valueAt(i).dump(pw);
+
+ pw.println("Active Source history:");
+ pw.increaseIndent();
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ ArrayBlockingQueue<HdmiCecController.Dumpable> activeSourceHistory =
+ mLocalDevices.valueAt(i).getActiveSourceHistory();
+ for (HdmiCecController.Dumpable activeSourceEvent : activeSourceHistory) {
+ activeSourceEvent.dump(pw, sdf);
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ HdmiUtils.dumpIterable(pw, "mDeviceInfos:", mSafeAllDeviceInfos);
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8bb89da5726f..ee86593916ae 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -71,10 +71,8 @@ import android.os.UserHandle;
import android.provider.Settings.Global;
import android.sysprop.HdmiProperties;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -91,7 +89,6 @@ import libcore.util.EmptyArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -177,6 +174,8 @@ public class HdmiControlService extends SystemService {
static final int STANDBY_SCREEN_OFF = 0;
static final int STANDBY_SHUTDOWN = 1;
+ private HdmiCecNetwork mHdmiCecNetwork;
+
// Logical address of the active source.
@GuardedBy("mLock")
protected final ActiveSource mActiveSource = new ActiveSource();
@@ -333,21 +332,6 @@ public class HdmiControlService extends SystemService {
@Nullable
private HdmiCecController mCecController;
- // HDMI port information. Stored in the unmodifiable list to keep the static information
- // from being modified.
- // This variable is null if the current device does not have hdmi input.
- @GuardedBy("mLock")
- private List<HdmiPortInfo> mPortInfo = null;
-
- // Map from path(physical address) to port ID.
- private UnmodifiableSparseIntArray mPortIdMap;
-
- // Map from port ID to HdmiPortInfo.
- private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
-
- // Map from port ID to HdmiDeviceInfo.
- private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
-
private HdmiCecMessageValidator mMessageValidator;
@ServiceThreadOnly
@@ -389,10 +373,6 @@ public class HdmiControlService extends SystemService {
@Nullable
private Looper mIoLooper;
- // Thread safe physical address
- @GuardedBy("mLock")
- private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
-
// Last input port before switching to the MHL port. Should switch back to this port
// when the mobile device sends the request one touch play with off.
// Gets invalidated if we go to other port/input.
@@ -507,6 +487,26 @@ public class HdmiControlService extends SystemService {
@Override
public void onStart() {
+ initService();
+ publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
+
+ if (mCecController != null) {
+ // Register broadcast receiver for power state change.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SHUTDOWN);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
+
+ // Register ContentObserver to monitor the settings change.
+ registerContentObserver();
+ }
+ mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
+ }
+
+ @VisibleForTesting
+ void initService() {
if (mIoLooper == null) {
mIoThread.start();
mIoLooper = mIoThread.getLooper();
@@ -521,13 +521,7 @@ public class HdmiControlService extends SystemService {
if (mCecController == null) {
mCecController = HdmiCecController.create(this, getAtomWriter());
}
- if (mCecController != null) {
- if (mHdmiControlEnabled) {
- initializeCec(INITIATED_BY_BOOT_UP);
- } else {
- mCecController.setOption(OptionKey.ENABLE_CEC, false);
- }
- } else {
+ if (mCecController == null) {
Slog.i(TAG, "Device does not support HDMI-CEC.");
return;
}
@@ -537,27 +531,18 @@ public class HdmiControlService extends SystemService {
if (!mMhlController.isReady()) {
Slog.i(TAG, "Device does not support MHL-control.");
}
+ mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
+ if (mHdmiControlEnabled) {
+ initializeCec(INITIATED_BY_BOOT_UP);
+ } else {
+ mCecController.setOption(OptionKey.ENABLE_CEC, false);
+ }
mMhlDevices = Collections.emptyList();
- initPortInfo();
+ mHdmiCecNetwork.initPortInfo();
if (mMessageValidator == null) {
mMessageValidator = new HdmiCecMessageValidator(this);
}
- publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
-
- if (mCecController != null) {
- // Register broadcast receiver for power state change.
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SHUTDOWN);
- filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
-
- // Register ContentObserver to monitor the settings change.
- registerContentObserver();
- }
- mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
}
private void bootCompleted() {
@@ -588,6 +573,15 @@ public class HdmiControlService extends SystemService {
}
@VisibleForTesting
+ void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) {
+ mHdmiCecNetwork = hdmiCecNetwork;
+ }
+
+ public HdmiCecNetwork getHdmiCecNetwork() {
+ return mHdmiCecNetwork;
+ }
+
+ @VisibleForTesting
void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
mMhlController = hdmiMhlController;
}
@@ -705,7 +699,7 @@ public class HdmiControlService extends SystemService {
break;
case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
for (int type : mLocalDevices) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice != null) {
localDevice.setAutoDeviceOff(enabled);
}
@@ -800,7 +794,7 @@ public class HdmiControlService extends SystemService {
// A container for [Device type, Local device info].
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
}
@@ -832,43 +826,48 @@ public class HdmiControlService extends SystemService {
for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
mCecController.allocateLogicalAddress(localDevice.getType(),
localDevice.getPreferredAddress(), new AllocateAddressCallback() {
- @Override
- public void onAllocated(int deviceType, int logicalAddress) {
- if (logicalAddress == Constants.ADDR_UNREGISTERED) {
- Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
- } else {
- // Set POWER_STATUS_ON to all local devices because they share lifetime
- // with system.
- HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
- HdmiControlManager.POWER_STATUS_ON);
- localDevice.setDeviceInfo(deviceInfo);
- mCecController.addLocalDevice(deviceType, localDevice);
- mCecController.addLogicalAddress(logicalAddress);
- allocatedDevices.add(localDevice);
- }
+ @Override
+ public void onAllocated(int deviceType, int logicalAddress) {
+ if (logicalAddress == Constants.ADDR_UNREGISTERED) {
+ Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType
+ + "]");
+ } else {
+ // Set POWER_STATUS_ON to all local devices because they share
+ // lifetime
+ // with system.
+ HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress,
+ deviceType,
+ HdmiControlManager.POWER_STATUS_ON);
+ localDevice.setDeviceInfo(deviceInfo);
+ mHdmiCecNetwork.addLocalDevice(deviceType, localDevice);
+ mCecController.addLogicalAddress(logicalAddress);
+ allocatedDevices.add(localDevice);
+ }
- // Address allocation completed for all devices. Notify each device.
- if (allocatingDevices.size() == ++finished[0]) {
- mAddressAllocated = true;
- if (initiatedBy != INITIATED_BY_HOTPLUG) {
- // In case of the hotplug we don't call onInitializeCecComplete()
- // since we reallocate the logical address only.
- onInitializeCecComplete(initiatedBy);
- }
- notifyAddressAllocated(allocatedDevices, initiatedBy);
- // Reinvoke the saved display status callback once the local device is ready.
- if (mDisplayStatusCallback != null) {
- queryDisplayStatus(mDisplayStatusCallback);
- mDisplayStatusCallback = null;
- }
- if (mOtpCallbackPendingAddressAllocation != null) {
- oneTouchPlay(mOtpCallbackPendingAddressAllocation);
- mOtpCallbackPendingAddressAllocation = null;
+ // Address allocation completed for all devices. Notify each device.
+ if (allocatingDevices.size() == ++finished[0]) {
+ mAddressAllocated = true;
+ if (initiatedBy != INITIATED_BY_HOTPLUG) {
+ // In case of the hotplug we don't call
+ // onInitializeCecComplete()
+ // since we reallocate the logical address only.
+ onInitializeCecComplete(initiatedBy);
+ }
+ notifyAddressAllocated(allocatedDevices, initiatedBy);
+ // Reinvoke the saved display status callback once the local
+ // device is ready.
+ if (mDisplayStatusCallback != null) {
+ queryDisplayStatus(mDisplayStatusCallback);
+ mDisplayStatusCallback = null;
+ }
+ if (mOtpCallbackPendingAddressAllocation != null) {
+ oneTouchPlay(mOtpCallbackPendingAddressAllocation);
+ mOtpCallbackPendingAddressAllocation = null;
+ }
+ mCecMessageBuffer.processMessages();
+ }
}
- mCecMessageBuffer.processMessages();
- }
- }
- });
+ });
}
}
@@ -888,88 +887,14 @@ public class HdmiControlService extends SystemService {
return mAddressAllocated;
}
- // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
- // keep them in one place.
- @ServiceThreadOnly
- @VisibleForTesting
- protected void initPortInfo() {
- assertRunOnServiceThread();
- HdmiPortInfo[] cecPortInfo = null;
-
- synchronized (mLock) {
- mPhysicalAddress = getPhysicalAddress();
- }
-
- // CEC HAL provides majority of the info while MHL does only MHL support flag for
- // each port. Return empty array if CEC HAL didn't provide the info.
- if (mCecController != null) {
- cecPortInfo = mCecController.getPortInfos();
- }
- if (cecPortInfo == null) {
- return;
- }
-
- SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
- SparseIntArray portIdMap = new SparseIntArray();
- SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
- for (HdmiPortInfo info : cecPortInfo) {
- portIdMap.put(info.getAddress(), info.getId());
- portInfoMap.put(info.getId(), info);
- portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
- }
- mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
- mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
- mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
-
- if (mMhlController == null) {
- return;
- }
- HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
- ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
- for (HdmiPortInfo info : mhlPortInfo) {
- if (info.isMhlSupported()) {
- mhlSupportedPorts.add(info.getId());
- }
- }
-
- // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
- // cec port info if we do not have have port that supports MHL.
- if (mhlSupportedPorts.isEmpty()) {
- setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
- return;
- }
- ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
- for (HdmiPortInfo info : cecPortInfo) {
- if (mhlSupportedPorts.contains(info.getId())) {
- result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
- info.isCecSupported(), true, info.isArcSupported()));
- } else {
- result.add(info);
- }
- }
- setPortInfo(Collections.unmodifiableList(result));
- }
-
List<HdmiPortInfo> getPortInfo() {
synchronized (mLock) {
- return mPortInfo;
- }
- }
-
- void setPortInfo(List<HdmiPortInfo> portInfo) {
- synchronized (mLock) {
- mPortInfo = portInfo;
+ return mHdmiCecNetwork.getPortInfo();
}
}
- /**
- * Returns HDMI port information for the given port id.
- *
- * @param portId HDMI port id
- * @return {@link HdmiPortInfo} for the given port
- */
HdmiPortInfo getPortInfo(int portId) {
- return mPortInfoMap.get(portId, null);
+ return mHdmiCecNetwork.getPortInfo(portId);
}
/**
@@ -977,12 +902,7 @@ public class HdmiControlService extends SystemService {
* port id.
*/
int portIdToPath(int portId) {
- HdmiPortInfo portInfo = getPortInfo(portId);
- if (portInfo == null) {
- Slog.e(TAG, "Cannot find the port info: " + portId);
- return Constants.INVALID_PHYSICAL_ADDRESS;
- }
- return portInfo.getAddress();
+ return mHdmiCecNetwork.portIdToPath(portId);
}
/**
@@ -999,26 +919,11 @@ public class HdmiControlService extends SystemService {
* on the current device.
*/
int pathToPortId(int path) {
- int mask = 0xF000;
- int finalMask = 0xF000;
- int physicalAddress;
- synchronized (mLock) {
- physicalAddress = mPhysicalAddress;
- }
- int maskedAddress = physicalAddress;
-
- while (maskedAddress != 0) {
- maskedAddress = physicalAddress & mask;
- finalMask |= mask;
- mask >>= 4;
- }
-
- int portAddress = path & finalMask;
- return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
+ return mHdmiCecNetwork.physicalAddressToPortId(path);
}
boolean isValidPortId(int portId) {
- return getPortInfo(portId) != null;
+ return mHdmiCecNetwork.getPortInfo(portId) != null;
}
/**
@@ -1068,7 +973,7 @@ public class HdmiControlService extends SystemService {
@ServiceThreadOnly
HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
assertRunOnServiceThread();
- return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
+ return mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
}
@ServiceThreadOnly
@@ -1092,11 +997,7 @@ public class HdmiControlService extends SystemService {
* Whether a device of the specified physical address is connected to ARC enabled port.
*/
boolean isConnectedToArcPort(int physicalAddress) {
- int portId = pathToPortId(physicalAddress);
- if (portId != Constants.INVALID_PORT_ID) {
- return mPortInfoMap.get(portId).isArcSupported();
- }
- return false;
+ return mHdmiCecNetwork.isConnectedToArcPort(physicalAddress);
}
@ServiceThreadOnly
@@ -1168,7 +1069,7 @@ public class HdmiControlService extends SystemService {
}
return true;
}
-
+ getHdmiCecNetwork().handleCecMessage(message);
if (dispatchMessageToLocalDevice(message)) {
return true;
}
@@ -1183,7 +1084,7 @@ public class HdmiControlService extends SystemService {
@ServiceThreadOnly
private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
assertRunOnServiceThread();
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
if (device.dispatchMessage(message)
&& message.getDestination() != Constants.ADDR_BROADCAST) {
return true;
@@ -1209,12 +1110,12 @@ public class HdmiControlService extends SystemService {
if (connected && !isTvDevice()
&& getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
if (isSwitchDevice()) {
- initPortInfo();
+ mHdmiCecNetwork.initPortInfo();
HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
}
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
localDevice.init();
@@ -1224,9 +1125,14 @@ public class HdmiControlService extends SystemService {
allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
}
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onHotplug(portId, connected);
}
+
+ if (!connected) {
+ mHdmiCecNetwork.removeDevicesConnectedToPort(portId);
+ }
+
announceHotplugEvent(portId, connected);
}
@@ -1262,7 +1168,7 @@ public class HdmiControlService extends SystemService {
List<HdmiCecLocalDevice> getAllLocalDevices() {
assertRunOnServiceThread();
- return mCecController.getLocalDeviceList();
+ return mHdmiCecNetwork.getLocalDeviceList();
}
/**
@@ -1275,8 +1181,14 @@ public class HdmiControlService extends SystemService {
*
* @param logicalAddress logical address of the remote device that might have the same logical
* address as the current device.
+ * @param physicalAddress physical address of the given device.
*/
- protected void checkLogicalAddressConflictAndReallocate(int logicalAddress) {
+ protected void checkLogicalAddressConflictAndReallocate(int logicalAddress,
+ int physicalAddress) {
+ // The given device is a local device. No logical address conflict.
+ if (physicalAddress == getPhysicalAddress()) {
+ return;
+ }
for (HdmiCecLocalDevice device : getAllLocalDevices()) {
if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) {
HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo());
@@ -1616,8 +1528,7 @@ public class HdmiControlService extends SystemService {
return null;
}
if (audioSystem() != null) {
- HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
- for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
+ for (HdmiDeviceInfo info : mHdmiCecNetwork.getSafeCecDevicesLocked()) {
if (info.getPhysicalAddress() == activeSource.physicalAddress) {
return info;
}
@@ -1649,7 +1560,7 @@ public class HdmiControlService extends SystemService {
}
int activePath = tv.getActivePath();
if (activePath != HdmiDeviceInfo.PATH_INVALID) {
- HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
+ HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath);
return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
}
return null;
@@ -1752,7 +1663,7 @@ public class HdmiControlService extends SystemService {
return;
}
if (mCecController != null) {
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType);
if (localDevice == null) {
Slog.w(TAG, "Local device not available to send key event.");
return;
@@ -1774,7 +1685,7 @@ public class HdmiControlService extends SystemService {
Slog.w(TAG, "CEC controller not available to send volume key event.");
return;
}
- HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType);
if (localDevice == null) {
Slog.w(TAG, "Local device " + deviceType
+ " not available to send volume key event.");
@@ -1888,7 +1799,7 @@ public class HdmiControlService extends SystemService {
public int getPhysicalAddress() {
enforceAccessPermission();
synchronized (mLock) {
- return mPhysicalAddress;
+ return mHdmiCecNetwork.getPhysicalAddress();
}
}
@@ -1934,13 +1845,8 @@ public class HdmiControlService extends SystemService {
enforceAccessPermission();
// No need to hold the lock for obtaining TV device as the local device instance
// is preserved while the HDMI control is enabled.
- HdmiCecLocalDeviceTv tv = tv();
- synchronized (mLock) {
- List<HdmiDeviceInfo> cecDevices = (tv == null)
- ? Collections.<HdmiDeviceInfo>emptyList()
- : tv.getSafeExternalInputsLocked();
- return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
- }
+ return HdmiUtils.mergeToUnmodifiableList(mHdmiCecNetwork.getSafeExternalInputsLocked(),
+ getMhlDevicesLocked());
}
// Returns all the CEC devices on the bus including system audio, switch,
@@ -1948,19 +1854,7 @@ public class HdmiControlService extends SystemService {
@Override
public List<HdmiDeviceInfo> getDeviceList() {
enforceAccessPermission();
- HdmiCecLocalDeviceTv tv = tv();
- if (tv != null) {
- synchronized (mLock) {
- return tv.getSafeCecDevicesLocked();
- }
- } else {
- HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
- synchronized (mLock) {
- return (audioSystem == null)
- ? Collections.<HdmiDeviceInfo>emptyList()
- : audioSystem.getSafeCecDevicesLocked();
- }
- }
+ return mHdmiCecNetwork.getSafeCecDevicesLocked();
}
@Override
@@ -2089,7 +1983,7 @@ public class HdmiControlService extends SystemService {
runOnServiceThread(new Runnable() {
@Override
public void run() {
- HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType);
if (device == null) {
Slog.w(TAG, "Local device not available");
return;
@@ -2117,7 +2011,7 @@ public class HdmiControlService extends SystemService {
mhlDevice.sendStandby();
return;
}
- HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType);
if (device == null) {
device = audioSystem();
}
@@ -2262,7 +2156,7 @@ public class HdmiControlService extends SystemService {
runOnServiceThread(new Runnable() {
@Override
public void run() {
- HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
+ HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType);
if (device == null) {
Slog.w(TAG, "Local device not available");
return;
@@ -2339,8 +2233,7 @@ public class HdmiControlService extends SystemService {
pw.increaseIndent();
mMhlController.dump(pw);
pw.decreaseIndent();
-
- HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+ mHdmiCecNetwork.dump(pw);
if (mCecController != null) {
pw.println("mCecController: ");
pw.increaseIndent();
@@ -2832,7 +2725,7 @@ public class HdmiControlService extends SystemService {
}
public HdmiCecLocalDeviceTv tv() {
- return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
+ return (HdmiCecLocalDeviceTv) mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
}
boolean isTvDevice() {
@@ -2857,11 +2750,11 @@ public class HdmiControlService extends SystemService {
protected HdmiCecLocalDevicePlayback playback() {
return (HdmiCecLocalDevicePlayback)
- mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
}
public HdmiCecLocalDeviceAudioSystem audioSystem() {
- return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
+ return (HdmiCecLocalDeviceAudioSystem) mHdmiCecNetwork.getLocalDevice(
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
@@ -2991,7 +2884,7 @@ public class HdmiControlService extends SystemService {
}
private boolean canGoToStandby() {
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
if (!device.canGoToStandby()) return false;
}
return true;
@@ -3025,7 +2918,7 @@ public class HdmiControlService extends SystemService {
private void disableDevices(PendingActionClearedCallback callback) {
if (mCecController != null) {
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.disableDevice(mStandbyMessageReceived, callback);
}
}
@@ -3039,7 +2932,8 @@ public class HdmiControlService extends SystemService {
return;
}
mCecController.clearLogicalAddress();
- mCecController.clearLocalDevices();
+ mHdmiCecNetwork.clearLogicalAddress();
+ mHdmiCecNetwork.clearLocalDevices();
}
@ServiceThreadOnly
@@ -3051,7 +2945,7 @@ public class HdmiControlService extends SystemService {
return;
}
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
- for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
device.onStandby(mStandbyMessageReceived, standbyAction);
}
mStandbyMessageReceived = false;
@@ -3411,7 +3305,7 @@ public class HdmiControlService extends SystemService {
// input change listener should be the one describing the corresponding HDMI port.
HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
HdmiDeviceInfo info = (device != null) ? device.getInfo()
- : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
+ : mHdmiCecNetwork.getDeviceForPortId(portId);
invokeInputChangeListener(info);
}
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 7670dccf9c0a..ece78bfa2769 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -148,7 +148,8 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
}
private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
- BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly);
+ BitSet currentInfos = infoListToBitSet(
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false), audioOnly);
BitSet polledResult = addressListToBitSet(ackedAddress);
// At first, check removed devices.
@@ -225,11 +226,11 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
mayCancelOneTouchRecord(removedAddress);
mayDisableSystemAudioAndARC(removedAddress);
- tv().removeCecDevice(removedAddress);
+ localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress);
}
private void mayChangeRoutingPath(int address) {
- HdmiDeviceInfo info = tv().getCecDeviceInfo(address);
+ HdmiDeviceInfo info = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(address);
if (info != null) {
tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 6753368911b9..edc7bd95a017 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -19,6 +19,7 @@ import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.Slog;
import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
+
import java.io.UnsupportedEncodingException;
/**
@@ -164,7 +165,8 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
private void addDeviceInfo() {
// The device should be in the device list with default information.
- if (!tv().isInDeviceList(mDeviceLogicalAddress, mDevicePhysicalAddress)) {
+ if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress,
+ mDevicePhysicalAddress)) {
Slog.w(TAG, String.format("Device not found (%02x, %04x)",
mDeviceLogicalAddress, mDevicePhysicalAddress));
return;
@@ -176,7 +178,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
mDeviceLogicalAddress, mDevicePhysicalAddress,
tv().getPortId(mDevicePhysicalAddress),
mDeviceType, mVendorId, mDisplayName);
- tv().addCecDevice(deviceInfo);
+ localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
// Consume CEC messages we already got for this newly found device.
tv().processDelayedMessages(mDeviceLogicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index e78a86c21453..53f9a109d08d 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -19,6 +19,7 @@ import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
+import android.provider.Settings.Global;
import android.util.Slog;
import java.util.ArrayList;
@@ -54,6 +55,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
private int mPowerStatusCounter = 0;
+ private HdmiCecLocalDeviceSource mSource;
+
// Factory method. Ensures arguments are valid.
static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
int targetAddress, IHdmiControlCallback callback) {
@@ -74,27 +77,33 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
@Override
boolean start() {
+ // Because only source device can create this action, it's safe to cast.
+ mSource = source();
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
broadcastActiveSource();
+ // If the device is not an audio system itself, request the connected audio system to
+ // turn on.
+ if (shouldTurnOnConnectedAudioSystem()) {
+ sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(),
+ Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true));
+ }
queryDevicePowerStatus();
addTimer(mState, HdmiConfig.TIMEOUT_MS);
return true;
}
private void broadcastActiveSource() {
- // Because only source device can create this action, it's safe to cast.
- HdmiCecLocalDeviceSource source = source();
- source.mService.setAndBroadcastActiveSourceFromOneDeviceType(
+ mSource.mService.setAndBroadcastActiveSourceFromOneDeviceType(
mTargetAddress, getSourcePath(), "OneTouchPlayAction#broadcastActiveSource()");
// When OneTouchPlay is called, client side should be responsible to send out the intent
// of which internal source, for example YouTube, it would like to switch to.
// Here we only update the active port and the active source records in the local
// device as well as claiming Active Source.
- if (source.mService.audioSystem() != null) {
- source = source.mService.audioSystem();
+ if (mSource.mService.audioSystem() != null) {
+ mSource = mSource.mService.audioSystem();
}
- source.setRoutingPort(Constants.CEC_SWITCH_HOME);
- source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
+ mSource.setRoutingPort(Constants.CEC_SWITCH_HOME);
+ mSource.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
private void queryDevicePowerStatus() {
@@ -151,4 +160,14 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
Slog.e(TAG, "Callback failed:" + e);
}
}
+
+ private boolean shouldTurnOnConnectedAudioSystem() {
+ HdmiControlService service = mSource.mService;
+ if (service.isAudioSystemDevice()) {
+ return false;
+ }
+ String sendStandbyOnSleep = service.readStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, "");
+ return sendStandbyOnSleep.equals(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index a62d0b63221c..909fcda26c39 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -20,7 +20,9 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.util.SparseIntArray;
+
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
import java.util.List;
/**
@@ -111,7 +113,8 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
}
private void queryPowerStatus() {
- List<HdmiDeviceInfo> deviceInfos = tv().getDeviceInfoList(false);
+ List<HdmiDeviceInfo> deviceInfos =
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
resetPowerStatus(deviceInfos);
for (HdmiDeviceInfo info : deviceInfos) {
final int logicalAddress = info.getLogicalAddress();
@@ -137,7 +140,8 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
}
private void updatePowerStatus(int logicalAddress, int newStatus, boolean remove) {
- tv().updateDevicePowerStatus(logicalAddress, newStatus);
+ localDevice().mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
+ newStatus);
if (remove) {
mPowerStatus.delete(logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
index 6c8694ea74ad..6c147ed5e6d6 100644
--- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
@@ -17,8 +17,8 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
@@ -160,7 +160,9 @@ final class RoutingControlAction extends HdmiCecFeatureAction {
}
switch (timeoutState) {
case STATE_WAIT_FOR_ROUTING_INFORMATION:
- HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath);
+ HdmiDeviceInfo device =
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath(
+ mCurrentRoutingPath);
if (device != null && mQueryDevicePowerStatus) {
int deviceLogicalAddress = device.getLogicalAddress();
queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index cdb73d8ad6a2..9ca4d35f4579 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -556,8 +556,8 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public void registerLocationListener(String provider, LocationRequest request,
- ILocationListener listener, String packageName, String attributionTag,
- String listenerId) {
+ ILocationListener listener, String packageName, @Nullable String attributionTag,
+ @Nullable String listenerId) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
listenerId);
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
@@ -582,7 +582,7 @@ public class LocationManagerService extends ILocationManager.Stub {
@Override
public void registerLocationPendingIntent(String provider, LocationRequest request,
- PendingIntent pendingIntent, String packageName, String attributionTag) {
+ PendingIntent pendingIntent, String packageName, @Nullable String attributionTag) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
AppOpsManager.toReceiverId(pendingIntent));
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 179fb7d2f723..b4a172393ba6 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -77,7 +77,6 @@ import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.Preconditions;
@@ -115,7 +114,6 @@ import java.util.Objects;
class LocationProviderManager extends
ListenerMultiplexer<Object, LocationProviderManager.LocationTransport,
- LocationProviderManager.LocationListenerOperation,
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
@@ -225,16 +223,8 @@ class LocationProviderManager extends
}
}
- protected interface LocationListenerOperation extends ListenerOperation<LocationTransport> {
- /**
- * Must be implemented to return the location this operation intends to deliver.
- */
- @Nullable
- Location getLocation();
- }
-
protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
- LocationTransport, LocationListenerOperation> {
+ LocationTransport> {
private final @PermissionLevel int mPermissionLevel;
@@ -310,7 +300,7 @@ class LocationProviderManager extends
protected void onProviderListenerUnregister() {}
@Override
- protected final LocationListenerOperation onActive() {
+ protected final void onActive() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -319,11 +309,12 @@ class LocationProviderManager extends
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
onHighPowerUsageChanged();
- return onProviderListenerActive();
+
+ onProviderListenerActive();
}
@Override
- protected final LocationListenerOperation onInactive() {
+ protected final void onInactive() {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -332,24 +323,21 @@ class LocationProviderManager extends
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
}
- return onProviderListenerInactive();
+
+ onProviderListenerInactive();
}
/**
* Subclasses may override this instead of {@link #onActive()}.
*/
@GuardedBy("mLock")
- protected LocationListenerOperation onProviderListenerActive() {
- return null;
- }
+ protected void onProviderListenerActive() {}
/**
* Subclasses may override this instead of {@link #onInactive()} ()}.
*/
@GuardedBy("mLock")
- protected LocationListenerOperation onProviderListenerInactive() {
- return null;
- }
+ protected void onProviderListenerInactive() {}
@Override
public final LocationRequest getRequest() {
@@ -357,10 +345,8 @@ class LocationProviderManager extends
}
@GuardedBy("mLock")
- final void initializeLastLocation(@Nullable Location location) {
- if (mLastLocation == null) {
- mLastLocation = location;
- }
+ final void setLastDeliveredLocation(@Nullable Location location) {
+ mLastLocation = location;
}
@GuardedBy("mLock")
@@ -541,16 +527,8 @@ class LocationProviderManager extends
}
@GuardedBy("mLock")
- @Override
- protected final LocationListenerOperation onExecuteOperation(
- LocationListenerOperation operation) {
- mLastLocation = operation.getLocation();
- return super.onExecuteOperation(operation);
- }
-
- @GuardedBy("mLock")
- @Nullable
- abstract LocationListenerOperation acceptLocationChange(Location fineLocation);
+ abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ Location fineLocation);
@Override
public String toString() {
@@ -656,7 +634,7 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
- protected final LocationListenerOperation onProviderListenerActive() {
+ protected final void onProviderListenerActive() {
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
@@ -679,12 +657,10 @@ class LocationProviderManager extends
getRequest().isLocationSettingsIgnored(),
maxLocationAgeMs);
if (lastLocation != null) {
- return acceptLocationChange(lastLocation);
+ executeOperation(acceptLocationChange(lastLocation));
}
}
}
-
- return null;
}
@Override
@@ -703,9 +679,9 @@ class LocationProviderManager extends
}
@GuardedBy("mLock")
- @Nullable
@Override
- LocationListenerOperation acceptLocationChange(Location fineLocation) {
+ @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ Location fineLocation) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -748,16 +724,20 @@ class LocationProviderManager extends
return null;
}
- return new LocationListenerOperation() {
- @Override
- public Location getLocation() {
- return location;
- }
+ // deliver location
+ return new ListenerOperation<LocationTransport>() {
+
+ private boolean mUseWakeLock;
@Override
public void onPreExecute() {
+ mUseWakeLock = !location.isFromMockProvider();
+
+ // update last delivered location
+ setLastDeliveredLocation(location);
+
// don't acquire a wakelock for mock locations to prevent abuse
- if (!location.isFromMockProvider()) {
+ if (mUseWakeLock) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
}
}
@@ -774,13 +754,13 @@ class LocationProviderManager extends
}
listener.deliverOnLocationChanged(deliveryLocation,
- location.isFromMockProvider() ? null : mWakeLock::release);
+ mUseWakeLock ? mWakeLock::release : null);
mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
}
@Override
public void onPostExecute(boolean success) {
- if (!success && !location.isFromMockProvider()) {
+ if (!success && mUseWakeLock) {
mWakeLock.release();
}
@@ -852,7 +832,8 @@ class LocationProviderManager extends
}
@Override
- public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+ public void onOperationFailure(ListenerOperation<LocationTransport> operation,
+ Exception exception) {
onTransportFailure(exception);
}
@@ -913,7 +894,8 @@ class LocationProviderManager extends
}
@Override
- public void onOperationFailure(LocationListenerOperation operation, Exception exception) {
+ public void onOperationFailure(ListenerOperation<LocationTransport> operation,
+ Exception exception) {
onTransportFailure(exception);
}
@@ -988,28 +970,24 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
- protected LocationListenerOperation onProviderListenerActive() {
+ protected void onProviderListenerActive() {
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
getRequest().isLocationSettingsIgnored(),
MAX_CURRENT_LOCATION_AGE_MS);
if (lastLocation != null) {
- return acceptLocationChange(lastLocation);
+ executeOperation(acceptLocationChange(lastLocation));
}
-
- return null;
}
@GuardedBy("mLock")
@Override
- protected LocationListenerOperation onProviderListenerInactive() {
+ protected void onProviderListenerInactive() {
if (!getRequest().isLocationSettingsIgnored()) {
// if we go inactive for any reason, fail immediately
- return acceptLocationChange(null);
+ executeOperation(acceptLocationChange(null));
}
-
- return null;
}
void deliverNull() {
@@ -1035,9 +1013,9 @@ class LocationProviderManager extends
}
@GuardedBy("mLock")
- @Nullable
@Override
- LocationListenerOperation acceptLocationChange(@Nullable Location fineLocation) {
+ @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ @Nullable Location fineLocation) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
@@ -1059,36 +1037,21 @@ class LocationProviderManager extends
Location location = getPermittedLocation(fineLocation, getPermissionLevel());
- return new LocationListenerOperation() {
- @Override
- public Location getLocation() {
- return location;
+ // deliver location
+ return listener -> {
+ // if delivering to the same process, make a copy of the location first (since
+ // location is mutable)
+ Location deliveryLocation = location;
+ if (getIdentity().getPid() == Process.myPid() && location != null) {
+ deliveryLocation = new Location(location);
}
- @Override
- public void operate(LocationTransport listener) {
- // if delivering to the same process, make a copy of the location first (since
- // location is mutable)
- Location deliveryLocation = location;
- if (getIdentity().getPid() == Process.myPid() && location != null) {
- deliveryLocation = new Location(location);
- }
-
- // we currently don't hold a wakelock for getCurrentLocation deliveries
- try {
- listener.deliverOnLocationChanged(deliveryLocation, null);
- mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
- } catch (Exception exception) {
- if (exception instanceof RemoteException) {
- Log.w(TAG, "registration " + this + " failed", exception);
- } else {
- throw new AssertionError(exception);
- }
- }
+ // we currently don't hold a wakelock for getCurrentLocation deliveries
+ listener.deliverOnLocationChanged(deliveryLocation, null);
+ mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
- synchronized (mLock) {
- remove();
- }
+ synchronized (mLock) {
+ remove();
}
};
}
@@ -1114,7 +1077,7 @@ class LocationProviderManager extends
protected final Object mLock = new Object();
protected final String mName;
- @Nullable private final PassiveLocationProviderManager mPassiveManager;
+ private final @Nullable PassiveLocationProviderManager mPassiveManager;
protected final Context mContext;
@@ -1178,7 +1141,7 @@ class LocationProviderManager extends
protected final MockableLocationProvider mProvider;
@GuardedBy("mLock")
- @Nullable private OnAlarmListener mDelayedRegister;
+ private @Nullable OnAlarmListener mDelayedRegister;
LocationProviderManager(Context context, Injector injector, String name,
@Nullable PassiveLocationProviderManager passiveManager) {
@@ -1254,13 +1217,11 @@ class LocationProviderManager extends
return mName;
}
- @Nullable
- public CallerIdentity getIdentity() {
+ public @Nullable CallerIdentity getIdentity() {
return mProvider.getState().identity;
}
- @Nullable
- public ProviderProperties getProperties() {
+ public @Nullable ProviderProperties getProperties() {
return mProvider.getState().properties;
}
@@ -1381,9 +1342,8 @@ class LocationProviderManager extends
}
}
- @Nullable
- public Location getLastLocation(CallerIdentity identity, @PermissionLevel int permissionLevel,
- boolean ignoreLocationSettings) {
+ public @Nullable Location getLastLocation(CallerIdentity identity,
+ @PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
identity.getPackageName())) {
return null;
@@ -1426,9 +1386,9 @@ class LocationProviderManager extends
* location, even if the permissionLevel is coarse. You are responsible for coarsening the
* location if necessary.
*/
- @Nullable
- public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
- boolean ignoreLocationSettings, long maximumAgeMs) {
+ public @Nullable Location getLastLocationUnsafe(int userId,
+ @PermissionLevel int permissionLevel, boolean ignoreLocationSettings,
+ long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
// find the most recent location across all users
Location lastLocation = null;
@@ -1500,8 +1460,7 @@ class LocationProviderManager extends
}
}
- @Nullable
- public ICancellationSignal getCurrentLocation(LocationRequest request,
+ public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
@@ -1519,7 +1478,7 @@ class LocationProviderManager extends
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- addRegistration(callback.asBinder(), registration);
+ putRegistration(callback.asBinder(), registration);
if (!registration.isActive()) {
// if the registration never activated, fail it immediately
registration.deliverNull();
@@ -1560,7 +1519,7 @@ class LocationProviderManager extends
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- addRegistration(listener.asBinder(), registration);
+ putRegistration(listener.asBinder(), registration);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1578,7 +1537,7 @@ class LocationProviderManager extends
synchronized (mLock) {
final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(pendingIntent, registration);
+ putRegistration(pendingIntent, registration);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1673,7 +1632,7 @@ class LocationProviderManager extends
Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
- newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation());
+ newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
super.onRegistrationReplaced(key, oldRegistration, newRegistration);
}
@@ -2056,7 +2015,9 @@ class LocationProviderManager extends
setLastLocation(location, UserHandle.USER_ALL);
// attempt listener delivery
- deliverToListeners(registration -> registration.acceptLocationChange(location));
+ deliverToListeners(registration -> {
+ return registration.acceptLocationChange(location);
+ });
// notify passive provider
if (mPassiveManager != null) {
@@ -2194,8 +2155,7 @@ class LocationProviderManager extends
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- @Nullable
- private Location getPermittedLocation(@Nullable Location fineLocation,
+ private @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
@PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2250,10 +2210,10 @@ class LocationProviderManager extends
private static class LastLocation {
- @Nullable private Location mFineLocation;
- @Nullable private Location mCoarseLocation;
- @Nullable private Location mFineBypassLocation;
- @Nullable private Location mCoarseBypassLocation;
+ private @Nullable Location mFineLocation;
+ private @Nullable Location mCoarseLocation;
+ private @Nullable Location mFineBypassLocation;
+ private @Nullable Location mCoarseBypassLocation;
public void clearMock() {
if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
@@ -2275,8 +2235,8 @@ class LocationProviderManager extends
mCoarseLocation = null;
}
- @Nullable
- public Location get(@PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
+ public @Nullable Location get(@PermissionLevel int permissionLevel,
+ boolean ignoreLocationSettings) {
switch (permissionLevel) {
case PERMISSION_FINE:
if (ignoreLocationSettings) {
@@ -2337,13 +2297,12 @@ class LocationProviderManager extends
private static class SingleUseCallback extends IRemoteCallback.Stub implements Runnable,
CancellationSignal.OnCancelListener {
- @Nullable
- public static SingleUseCallback wrap(@Nullable Runnable callback) {
+ public static @Nullable SingleUseCallback wrap(@Nullable Runnable callback) {
return callback == null ? null : new SingleUseCallback(callback);
}
@GuardedBy("this")
- @Nullable private Runnable mCallback;
+ private @Nullable Runnable mCallback;
private SingleUseCallback(Runnable callback) {
mCallback = Objects.requireNonNull(callback);
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index c91ee824ff61..7a59cba02dd9 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -41,7 +41,6 @@ import android.stats.location.LocationStatsEnums;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.PendingIntentUtils;
import com.android.server.location.LocationPermissions;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -60,8 +59,8 @@ import java.util.Objects;
* Manages all geofences.
*/
public class GeofenceManager extends
- ListenerMultiplexer<GeofenceKey, PendingIntent, ListenerOperation<PendingIntent>,
- GeofenceManager.GeofenceRegistration, LocationRequest> implements
+ ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
+ LocationRequest> implements
LocationListener {
private static final String TAG = "GeofenceManager";
@@ -121,12 +120,10 @@ public class GeofenceManager extends
}
@Override
- protected ListenerOperation<PendingIntent> onActive() {
+ protected void onActive() {
Location location = getLastLocation();
if (location != null) {
- return onLocationChanged(location);
- } else {
- return null;
+ executeOperation(onLocationChanged(location));
}
}
@@ -304,7 +301,7 @@ public class GeofenceManager extends
final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(new GeofenceKey(pendingIntent, geofence),
+ putRegistration(new GeofenceKey(pendingIntent, geofence),
new GeofenceRegistration(geofence, callerIdentity, pendingIntent));
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index ec48d4cbcecd..7592d22a3a78 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -32,7 +32,6 @@ import android.os.IInterface;
import android.os.Process;
import android.util.ArraySet;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.location.listeners.BinderListenerRegistration;
@@ -60,7 +59,7 @@ import java.util.Objects;
*/
public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInterface,
TMergedRegistration> extends
- ListenerMultiplexer<IBinder, TListener, ListenerOperation<TListener>,
+ ListenerMultiplexer<IBinder, TListener,
GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration>
.GnssListenerRegistration, TMergedRegistration> {
@@ -231,7 +230,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
TListener listener) {
final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(listener.asBinder(),
+ putRegistration(listener.asBinder(),
createRegistration(request, callerIdentity, listener));
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 74284f357f61..4a3f94f9b73a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -63,18 +63,16 @@ public final class GnssMeasurementsProvider extends
@Nullable
@Override
- protected ListenerOperation<IGnssMeasurementsListener> onActive() {
+ protected void onActive() {
mLocationAttributionHelper.reportHighPowerLocationStart(
getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey());
- return null;
}
@Nullable
@Override
- protected ListenerOperation<IGnssMeasurementsListener> onInactive() {
+ protected void onInactive() {
mLocationAttributionHelper.reportHighPowerLocationStop(
getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey());
- return null;
}
}
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index bc675ceda970..d6b179bab5a2 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -23,8 +23,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
/**
* A registration that works with IBinder keys, and registers a DeathListener to automatically
* remove the registration if the binder dies. The key for this registration must either be an
@@ -34,7 +32,7 @@ import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
* @param <TListener> listener type
*/
public abstract class BinderListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
+ RemoteListenerRegistration<TRequest, TListener> implements
Binder.DeathRecipient {
/**
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 0318ffb519ba..6b936169c82f 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -75,13 +75,11 @@ import java.util.function.Predicate;
*
* @param <TKey> key type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
* @param <TRegistration> registration type
* @param <TMergedRegistration> merged registration type
*/
public abstract class ListenerMultiplexer<TKey, TListener,
- TListenerOperation extends ListenerOperation<TListener>,
- TRegistration extends ListenerRegistration<TListener, TListenerOperation>,
+ TRegistration extends ListenerRegistration<TListener>,
TMergedRegistration> {
@GuardedBy("mRegistrations")
@@ -218,10 +216,26 @@ public abstract class ListenerMultiplexer<TKey, TListener,
protected void onInactive() {}
/**
- * Adds a new registration with the given key. This method cannot be called to add a
- * registration re-entrantly.
+ * Puts a new registration with the given key, replacing any previous registration under the
+ * same key. This method cannot be called to put a registration re-entrantly.
+ */
+ protected final void putRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
+ replaceRegistration(key, key, registration);
+ }
+
+ /**
+ * Atomically removes the registration with the old key and adds a new registration with the
+ * given key. If there was a registration for the old key,
+ * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
+ * invoked for the new registration and key instead of
+ * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
+ * the same key. The old key may be the same value as the new key, in which case this function
+ * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
+ * be called to add a registration re-entrantly.
*/
- protected final void addRegistration(@NonNull TKey key, @NonNull TRegistration registration) {
+ protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
+ @NonNull TRegistration registration) {
+ Objects.requireNonNull(oldKey);
Objects.requireNonNull(key);
Objects.requireNonNull(registration);
@@ -229,6 +243,9 @@ public abstract class ListenerMultiplexer<TKey, TListener,
// adding listeners reentrantly is not supported
Preconditions.checkState(!mReentrancyGuard.isReentrant());
+ // new key may only have a prior registration if the oldKey is the same as the key
+ Preconditions.checkArgument(oldKey == key || !mRegistrations.containsKey(key));
+
// since adding a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. further, we buffer service updates since adding a registration may
@@ -241,9 +258,11 @@ public abstract class ListenerMultiplexer<TKey, TListener,
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
- int index = mRegistrations.indexOfKey(key);
+ int index = mRegistrations.indexOfKey(oldKey);
if (index >= 0) {
- oldRegistration = removeRegistration(index, false);
+ oldRegistration = removeRegistration(index, oldKey != key);
+ }
+ if (oldKey == key && index >= 0) {
mRegistrations.setValueAt(index, registration);
} else {
mRegistrations.put(key, registration);
@@ -316,7 +335,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* re-entrancy, and may be called to remove a registration re-entrantly.
*/
protected final void removeRegistration(@NonNull Object key,
- @NonNull ListenerRegistration<?, ?> registration) {
+ @NonNull ListenerRegistration<?> registration) {
synchronized (mRegistrations) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
@@ -478,15 +497,9 @@ public abstract class ListenerMultiplexer<TKey, TListener,
if (++mActiveRegistrationsCount == 1) {
onActive();
}
- TListenerOperation operation = registration.onActive();
- if (operation != null) {
- execute(registration, operation);
- }
+ registration.onActive();
} else {
- TListenerOperation operation = registration.onInactive();
- if (operation != null) {
- execute(registration, operation);
- }
+ registration.onInactive();
if (--mActiveRegistrationsCount == 0) {
onInactive();
}
@@ -502,16 +515,16 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* change the active state of the registration.
*/
protected final void deliverToListeners(
- @NonNull Function<TRegistration, TListenerOperation> function) {
+ @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
- TListenerOperation operation = function.apply(registration);
+ ListenerOperation<TListener> operation = function.apply(registration);
if (operation != null) {
- execute(registration, operation);
+ registration.executeOperation(operation);
}
}
}
@@ -526,14 +539,14 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* deliverToListeners(registration -> operation);
* </pre>
*/
- protected final void deliverToListeners(@NonNull TListenerOperation operation) {
+ protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
synchronized (mRegistrations) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
- execute(registration, operation);
+ registration.executeOperation(operation);
}
}
}
@@ -545,10 +558,6 @@ public abstract class ListenerMultiplexer<TKey, TListener,
onRegistrationActiveChanged(registration);
}
- private void execute(TRegistration registration, TListenerOperation operation) {
- registration.executeInternal(operation);
- }
-
/**
* Dumps debug information.
*/
@@ -606,7 +615,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
@GuardedBy("mRegistrations")
private int mGuardCount;
@GuardedBy("mRegistrations")
- private @Nullable ArraySet<Entry<Object, ListenerRegistration<?, ?>>> mScheduledRemovals;
+ private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
@@ -622,7 +631,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
@GuardedBy("mRegistrations")
- void markForRemoval(Object key, ListenerRegistration<?, ?> registration) {
+ void markForRemoval(Object key, ListenerRegistration<?> registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
@@ -641,7 +650,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
@Override
public void close() {
- ArraySet<Entry<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null;
+ ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
Preconditions.checkState(mGuardCount > 0);
if (--mGuardCount == 0) {
@@ -656,7 +665,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
final int size = scheduledRemovals.size();
for (int i = 0; i < size; i++) {
- Entry<Object, ListenerRegistration<?, ?>> entry = scheduledRemovals.valueAt(i);
+ Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
removeRegistration(entry.getKey(), entry.getValue());
}
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index d7ecbcb7cfdf..fa21b3a8e369 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -17,11 +17,9 @@
package com.android.server.location.listeners;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.listeners.ListenerExecutor;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -31,11 +29,8 @@ import java.util.concurrent.Executor;
* request, and an executor responsible for listener invocations.
*
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public class ListenerRegistration<TListener,
- TListenerOperation extends ListenerOperation<TListener>> implements
- ListenerExecutor {
+public class ListenerRegistration<TListener> implements ListenerExecutor {
private final Executor mExecutor;
@@ -70,18 +65,14 @@ public class ListenerRegistration<TListener,
* returns a non-null operation, that operation will be invoked for the listener. Invoked
* while holding the owning multiplexer's internal lock.
*/
- protected @Nullable TListenerOperation onActive() {
- return null;
- }
+ protected void onActive() {}
/**
* May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
* a non-null operation, that operation will be invoked for the listener. Invoked while holding
* the owning multiplexer's internal lock.
*/
- protected @Nullable TListenerOperation onInactive() {
- return null;
- }
+ protected void onInactive() {}
public final boolean isActive() {
return mActive;
@@ -114,27 +105,20 @@ public class ListenerRegistration<TListener,
protected void onListenerUnregister() {}
/**
- * May be overridden by subclasses, however should rarely be needed. Invoked whenever a listener
- * operation is submitted for execution, and allows the registration a chance to replace the
- * listener operation or perform related bookkeeping. There is no guarantee a listener operation
- * submitted or returned here will ever be invoked. Will always be invoked on the calling
- * thread.
- */
- protected TListenerOperation onExecuteOperation(@NonNull TListenerOperation operation) {
- return operation;
- }
-
- /**
* May be overridden by subclasses to handle listener operation failures. The default behavior
* is to further propagate any exceptions. Will always be invoked on the executor thread.
*/
- protected void onOperationFailure(TListenerOperation operation, Exception exception) {
- throw new AssertionError(exception);
+ protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+ throw new AssertionError(e);
}
- final void executeInternal(@NonNull TListenerOperation operation) {
- executeSafely(mExecutor, () -> mListener,
- onExecuteOperation(Objects.requireNonNull(operation)), this::onOperationFailure);
+ /**
+ * Executes the given listener operation, invoking
+ * {@link #onOperationFailure(ListenerOperation, Exception)} in case the listener operation
+ * fails.
+ */
+ protected final void executeOperation(@Nullable ListenerOperation<TListener> operation) {
+ executeSafely(mExecutor, () -> mListener, operation, this::onOperationFailure);
}
@Override
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index e57b5322de8d..0aafb2929d56 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -21,8 +21,6 @@ import android.app.PendingIntent;
import android.location.util.identity.CallerIdentity;
import android.util.Log;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
/**
* A registration that works with PendingIntent keys, and registers a CancelListener to
* automatically remove the registration if the PendingIntent is canceled. The key for this
@@ -32,8 +30,7 @@ import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
* @param <TListener> listener type
*/
public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements
- PendingIntent.CancelListener {
+ RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
/**
* Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
@@ -73,7 +70,7 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
protected void onPendingIntentListenerUnregister() {}
@Override
- public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
+ protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof PendingIntent.CanceledException) {
Log.w(getOwner().getTag(), "registration " + this + " removed", e);
remove();
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
index 242bf323f6cd..4eca577dcf4f 100644
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
@@ -24,7 +24,6 @@ import android.location.util.identity.CallerIdentity;
import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.FgThread;
import java.util.Objects;
@@ -38,11 +37,9 @@ import java.util.concurrent.Executor;
*
* @param <TRequest> request type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public abstract class RemoteListenerRegistration<TRequest, TListener,
- TListenerOperation extends ListenerOperation<TListener>> extends
- RemovableListenerRegistration<TRequest, TListener, TListenerOperation> {
+public abstract class RemoteListenerRegistration<TRequest, TListener> extends
+ RemovableListenerRegistration<TRequest, TListener> {
@VisibleForTesting
public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index d3b5f6696167..618ff24b873b 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -18,8 +18,6 @@ package com.android.server.location.listeners;
import android.annotation.Nullable;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -29,11 +27,9 @@ import java.util.concurrent.Executor;
*
* @param <TRequest> request type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public abstract class RemovableListenerRegistration<TRequest, TListener,
- TListenerOperation extends ListenerOperation<TListener>> extends
- RequestListenerRegistration<TRequest, TListener, TListenerOperation> {
+public abstract class RemovableListenerRegistration<TRequest, TListener> extends
+ RequestListenerRegistration<TRequest, TListener> {
private volatile @Nullable Object mKey;
@@ -47,8 +43,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener,
* with. Often this is easiest to accomplish by defining registration subclasses as non-static
* inner classes of the multiplexer they are to be used with.
*/
- protected abstract ListenerMultiplexer<?, ? super TListener, ?
- super TListenerOperation, ?, ?> getOwner();
+ protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
index d97abae59dd3..0c2fc9142d92 100644
--- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
@@ -16,8 +16,6 @@
package com.android.server.location.listeners;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
-
import java.util.concurrent.Executor;
/**
@@ -25,11 +23,9 @@ import java.util.concurrent.Executor;
*
* @param <TRequest> request type
* @param <TListener> listener type
- * @param <TListenerOperation> listener operation type
*/
-public class RequestListenerRegistration<TRequest, TListener,
- TListenerOperation extends ListenerOperation<TListener>> extends
- ListenerRegistration<TListener, TListenerOperation> {
+public class RequestListenerRegistration<TRequest, TListener> extends
+ ListenerRegistration<TListener> {
private final TRequest mRequest;
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index 26ace47776be..72803ac35a3f 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -119,6 +119,33 @@ public final class IncrementalStates {
}
}
+ /**
+ * Change the startable state if the app has crashed or ANR'd during loading.
+ * If the app is not loading (i.e., fully loaded), this event doesn't change startable state.
+ */
+ public void onCrashOrAnr() {
+ if (DEBUG) {
+ Slog.i(TAG, "received package crash or ANR event");
+ }
+ final boolean startableStateChanged;
+ synchronized (mLock) {
+ if (mStartableState.isStartable() && mLoadingState.isLoading()) {
+ // Changing from startable -> unstartable only if app is still loading.
+ mStartableState.adoptNewStartableStateLocked(false);
+ startableStateChanged = true;
+ } else {
+ // If the app is fully loaded, the crash or ANR is caused by the app itself, so
+ // we do not change the startable state.
+ startableStateChanged = false;
+ }
+ }
+ if (startableStateChanged) {
+ mHandler.post(PooledLambda.obtainRunnable(
+ IncrementalStates::reportStartableState,
+ IncrementalStates.this).recycleOnUse());
+ }
+ }
+
private void reportStartableState() {
final Callback callback;
final boolean startable;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 410fbe57bf26..1896c9f08f83 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -336,6 +336,7 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote;
import com.android.internal.telephony.CarrierAppUtils;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -12687,12 +12688,17 @@ public class PackageManagerService extends IPackageManager.Stub
mPermissionManager.addAllPermissionGroups(pkg, chatty);
}
+ // If a permission has had its defining app changed, or it has had its protection
+ // upgraded, we need to revoke apps that hold it
+ final List<String> permissionsWithChangedDefinition;
// Don't allow ephemeral applications to define new permissions.
if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+ permissionsWithChangedDefinition = null;
Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
+ " ignored: instant apps cannot define new permissions.");
} else {
- mPermissionManager.addAllPermissions(pkg, chatty);
+ permissionsWithChangedDefinition =
+ mPermissionManager.addAllPermissions(pkg, chatty);
}
int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
@@ -12721,7 +12727,10 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- if (oldPkg != null) {
+ boolean hasOldPkg = oldPkg != null;
+ boolean hasPermissionDefinitionChanges =
+ !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
+ if (hasOldPkg || hasPermissionDefinitionChanges) {
// We need to call revokeRuntimePermissionsIfGroupChanged async as permission
// revoke callbacks from this method might need to kill apps which need the
// mPackages lock on a different thread. This would dead lock.
@@ -12732,9 +12741,16 @@ public class PackageManagerService extends IPackageManager.Stub
// won't be granted yet, hence new packages are no problem.
final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet());
- AsyncTask.execute(() ->
+ AsyncTask.execute(() -> {
+ if (hasOldPkg) {
mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg,
- allPackageNames));
+ allPackageNames);
+ }
+ if (hasPermissionDefinitionChanges) {
+ mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ permissionsWithChangedDefinition, allPackageNames);
+ }
+ });
}
}
@@ -25843,6 +25859,20 @@ public class PackageManagerService extends IPackageManager.Stub
}
return ps.getIncrementalStates();
}
+
+ @Override
+ public void notifyPackageCrashOrAnr(@NonNull String packageName) {
+ final PackageSetting ps;
+ synchronized (mLock) {
+ ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(TAG, "Failed notifyPackageCrash. Package " + packageName
+ + " is not installed");
+ return;
+ }
+ }
+ ps.setStatesOnCrashOrAnr();
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index be7c7c6ff1d6..ac76cf71ef67 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -772,6 +772,14 @@ public abstract class PackageSettingBase extends SettingBase {
}
/**
+ * Called to indicate that the running app has crashed or ANR'd. This might change the startable
+ * state of the package, depending on whether the package is fully loaded.
+ */
+ public void setStatesOnCrashOrAnr() {
+ incrementalStates.onCrashOrAnr();
+ }
+
+ /**
* Called to set the callback to listen for startable state changes.
*/
public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 6ffc5983417a..155d71673e06 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -74,6 +74,8 @@ public final class BasePermission {
private static final String ATTR_PACKAGE = "package";
private static final String TAG_ITEM = "item";
+ private boolean mPermissionDefinitionChanged;
+
@NonNull
private PermissionInfo mPermissionInfo;
@@ -122,6 +124,10 @@ public final class BasePermission {
return mPermissionInfo.packageName;
}
+ public boolean isPermissionDefinitionChanged() {
+ return mPermissionDefinitionChanged;
+ }
+
public int getType() {
return mType;
}
@@ -148,6 +154,10 @@ public final class BasePermission {
mReconciled = permissionInfo != null;
}
+ public void setPermissionDefinitionChanged(boolean shouldOverride) {
+ mPermissionDefinitionChanged = shouldOverride;
+ }
+
public boolean hasGids() {
return mGids.length != 0;
}
@@ -364,6 +374,7 @@ public final class BasePermission {
@NonNull AndroidPackage pkg, Collection<BasePermission> permissionTrees,
boolean chatty) {
// Allow system apps to redefine non-system permissions
+ boolean ownerChanged = false;
if (bp != null && !Objects.equals(bp.mPermissionInfo.packageName, p.packageName)) {
final boolean currentOwnerIsSystem;
if (!bp.mReconciled) {
@@ -389,6 +400,7 @@ public final class BasePermission {
String msg = "New decl " + pkg + " of permission "
+ p.name + " is system; overriding " + bp.mPermissionInfo.packageName;
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ ownerChanged = true;
bp = null;
}
}
@@ -396,6 +408,7 @@ public final class BasePermission {
if (bp == null) {
bp = new BasePermission(p.name, p.packageName, TYPE_MANIFEST);
}
+ boolean wasNormal = bp.isNormal();
StringBuilder r = null;
if (!bp.mReconciled) {
if (bp.mPermissionInfo.packageName == null
@@ -435,6 +448,11 @@ public final class BasePermission {
r.append("DUP:");
r.append(p.name);
}
+ if (bp.isRuntime() && (ownerChanged || wasNormal)) {
+ // If this is a runtime permission and the owner has changed, or this was a normal
+ // permission, then permission state should be cleaned up
+ bp.mPermissionDefinitionChanged = true;
+ }
if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
Log.d(TAG, " Permissions: " + r);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 4d43969578cd..da4ef63d6945 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2327,8 +2327,74 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- private void addAllPermissions(AndroidPackage pkg, boolean chatty) {
+ /**
+ * If permissions are upgraded to runtime, or their owner changes to the system, then any
+ * granted permissions must be revoked.
+ *
+ * @param permissionsToRevoke A list of permission names to revoke
+ * @param allPackageNames All package names
+ * @param permissionCallback Callback for permission changed
+ */
+ private void revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ @NonNull List<String> permissionsToRevoke,
+ @NonNull ArrayList<String> allPackageNames,
+ @NonNull PermissionCallback permissionCallback) {
+
+ final int[] userIds = mUserManagerInt.getUserIds();
+ final int numPermissions = permissionsToRevoke.size();
+ final int numUserIds = userIds.length;
+ final int numPackages = allPackageNames.size();
+ final int callingUid = Binder.getCallingUid();
+
+ for (int permNum = 0; permNum < numPermissions; permNum++) {
+ String permName = permissionsToRevoke.get(permNum);
+ BasePermission bp = mSettings.getPermission(permName);
+ if (bp == null || !bp.isRuntime()) {
+ continue;
+ }
+ for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
+ final int userId = userIds[userIdNum];
+ for (int packageNum = 0; packageNum < numPackages; packageNum++) {
+ final String packageName = allPackageNames.get(packageNum);
+ final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+ if (uid < Process.FIRST_APPLICATION_UID) {
+ // do not revoke from system apps
+ continue;
+ }
+ final int permissionState = checkPermissionImpl(permName, packageName,
+ userId);
+ final int flags = getPermissionFlags(permName, packageName, userId);
+ final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_POLICY_FIXED
+ | FLAG_PERMISSION_GRANTED_BY_DEFAULT
+ | FLAG_PERMISSION_GRANTED_BY_ROLE;
+ if (permissionState == PackageManager.PERMISSION_GRANTED
+ && (flags & flagMask) == 0) {
+ EventLog.writeEvent(0x534e4554, "154505240", uid,
+ "Revoking permission " + permName + " from package "
+ + packageName + " due to definition change");
+ EventLog.writeEvent(0x534e4554, "168319670", uid,
+ "Revoking permission " + permName + " from package "
+ + packageName + " due to definition change");
+ Slog.e(TAG, "Revoking permission " + permName + " from package "
+ + packageName + " due to definition change");
+ try {
+ revokeRuntimePermissionInternal(permName, packageName,
+ false, callingUid, userId, null, permissionCallback);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not revoke " + permName + " from "
+ + packageName, e);
+ }
+ }
+ }
+ }
+ bp.setPermissionDefinitionChanged(false);
+ }
+ }
+
+ private List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
final int N = ArrayUtils.size(pkg.getPermissions());
+ ArrayList<String> definitionChangedPermissions = new ArrayList<>();
for (int i=0; i<N; i++) {
ParsedPermission p = pkg.getPermissions().get(i);
@@ -2369,8 +2435,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (bp.isInstalled()) {
p.setFlags(p.getFlags() | PermissionInfo.FLAG_INSTALLED);
}
+ if (bp.isPermissionDefinitionChanged()) {
+ definitionChangedPermissions.add(p.getName());
+ }
}
}
+ return definitionChangedPermissions;
}
private void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
@@ -4750,9 +4820,18 @@ public class PermissionManagerService extends IPermissionManager.Stub {
PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage,
oldPackage, allPackageNames, mDefaultPermissionCallback);
}
+
+ @Override
+ public void revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ @NonNull List<String> permissionsToRevoke,
+ @NonNull ArrayList<String> allPackageNames) {
+ PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ permissionsToRevoke, allPackageNames, mDefaultPermissionCallback);
+ }
+
@Override
- public void addAllPermissions(AndroidPackage pkg, boolean chatty) {
- PermissionManagerService.this.addAllPermissions(pkg, chatty);
+ public List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) {
+ return PermissionManagerService.this.addAllPermissions(pkg, chatty);
}
@Override
public void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 5ea3458fcbfa..20e9c5dcb521 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -256,12 +256,26 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager
@NonNull ArrayList<String> allPackageNames);
/**
+ * Some permissions might have been owned by a non-system package, and the system then defined
+ * said permission. Some other permissions may one have been install permissions, but are now
+ * runtime or higher. These permissions should be revoked.
+ *
+ * @param permissionsToRevoke A list of permission names to revoke
+ * @param allPackageNames All packages
+ */
+ public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged(
+ @NonNull List<String> permissionsToRevoke,
+ @NonNull ArrayList<String> allPackageNames);
+
+ /**
* Add all permissions in the given package.
* <p>
* NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
* the permission settings.
+ *
+ * @return A list of BasePermissions that were updated, and need to be revoked from packages
*/
- public abstract void addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
+ public abstract List<String> addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty);
public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d01bc815c28d..0a7f08bfbe8c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1362,6 +1362,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else if (mLetterbox != null) {
mLetterbox.hide();
}
+ task.maybeUpdateLetterboxBounds(this, getLetterboxParams(w));
}
void updateLetterboxSurface(WindowState winHint) {
@@ -1375,6 +1376,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ @Nullable
+ private Rect getLetterboxParams(WindowState w) {
+ boolean isLetterboxed = w.isLetterboxedAppWindow() && fillsParent();
+ return isLetterboxed ? getBounds() : null;
+ }
+
Rect getLetterboxInsets() {
if (mLetterbox != null) {
return mLetterbox.getInsets();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8e5add916620..c582e6c8cb29 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -39,6 +39,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ApplicationInfo.FLAG_FACTORY_TEST;
import static android.content.pm.ConfigurationInfo.GL_ES_VERSION_UNDEFINED;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
@@ -1295,6 +1296,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8da6c813f591..7641de5ab7b9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5018,6 +5018,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
+ static boolean canReuseExistingTask(int windowingMode, int activityType) {
+ // Existing Tasks can be reused if a new stack will be created anyway, or for the Dream -
+ // because there can only ever be one DreamActivity.
+ return alwaysCreateStack(windowingMode, activityType)
+ || activityType == ACTIVITY_TYPE_DREAM;
+ }
+
@Nullable
Task getFocusedStack() {
return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedStack);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 132029f36096..6ffd9a28c10a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -528,6 +528,11 @@ class Task extends WindowContainer<WindowContainer> {
// {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root activity.
boolean mSupportsPictureInPicture;
+ // Activity bounds if this task or its top activity is presented in letterbox mode and
+ // {@code null} otherwise.
+ @Nullable
+ private Rect mLetterboxActivityBounds;
+
// Whether the task is currently being drag-resized
private boolean mDragResizing;
private int mDragResizeMode;
@@ -790,6 +795,10 @@ class Task extends WindowContainer<WindowContainer> {
*/
boolean mTaskAppearedSent;
+ // If the sending of the task appear signal should be deferred until this flag is set back to
+ // false.
+ private boolean mDeferTaskAppear;
+
/**
* This task was created by the task organizer which has the following implementations.
* <ul>
@@ -802,14 +811,20 @@ class Task extends WindowContainer<WindowContainer> {
@VisibleForTesting
boolean mCreatedByOrganizer;
+ // Tracking cookie for the creation of this task.
+ IBinder mLaunchCookie;
+
/**
* Don't use constructor directly. Use {@link TaskDisplayArea#createStackUnchecked()} instead.
*/
- Task(ActivityTaskManagerService atmService, int id, int activityType,
- ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+ Task(ActivityTaskManagerService atmService, int id, int activityType, ActivityInfo info,
+ Intent intent, boolean createdByOrganizer, boolean deferTaskAppear,
+ IBinder launchCookie) {
this(atmService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
null /*taskDescription*/, null /*stack*/);
mCreatedByOrganizer = createdByOrganizer;
+ mLaunchCookie = launchCookie;
+ mDeferTaskAppear = deferTaskAppear;
setActivityType(activityType);
}
@@ -2821,16 +2836,25 @@ class Task extends WindowContainer<WindowContainer> {
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+ final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
// Resolve override windowing mode to fullscreen for home task (even on freeform
// display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
- final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
+ // Do not allow non-resizable non-pinned tasks to be in a multi-window mode - they should
+ // use their parent's windowing mode, or fullscreen.
+ if (!isResizeable() && windowingMode != WINDOWING_MODE_PINNED
+ && WindowConfiguration.inMultiWindowMode(windowingMode)) {
+ windowingMode = WindowConfiguration.inMultiWindowMode(parentWindowingMode)
+ ? WINDOWING_MODE_FULLSCREEN : parentWindowingMode;
+ getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+ }
+
if (isLeafTask()) {
resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */);
}
@@ -2921,13 +2945,27 @@ class Task extends WindowContainer<WindowContainer> {
final int parentWidth = parentBounds.width();
final int parentHeight = parentBounds.height();
- final float aspect = ((float) parentHeight) / parentWidth;
+ float aspect = Math.max(parentWidth, parentHeight)
+ / (float) Math.min(parentWidth, parentHeight);
+
+ // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the
+ // extra available space.
+ if (refActivity != null) {
+ final float maxAspectRatio = refActivity.info.maxAspectRatio;
+ final float minAspectRatio = refActivity.info.minAspectRatio;
+ if (aspect > maxAspectRatio && maxAspectRatio != 0) {
+ aspect = maxAspectRatio;
+ } else if (aspect < minAspectRatio) {
+ aspect = minAspectRatio;
+ }
+ }
+
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- final int height = (int) (parentWidth / aspect);
+ final int height = (int) Math.rint(parentWidth / aspect);
final int top = parentBounds.centerY() - height / 2;
outBounds.set(parentBounds.left, top, parentBounds.right, top + height);
} else {
- final int width = (int) (parentHeight * aspect);
+ final int width = (int) Math.rint(parentHeight / aspect);
final int left = parentBounds.centerX() - width / 2;
outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
}
@@ -3333,7 +3371,9 @@ class Task extends WindowContainer<WindowContainer> {
}
boolean isResizeable(boolean checkSupportsPip) {
- return (mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
+ final boolean forceResizable = mAtmService.mForceResizableActivities
+ && getActivityType() == ACTIVITY_TYPE_STANDARD;
+ return (forceResizable || ActivityInfo.isResizeableMode(mResizeMode)
|| (checkSupportsPip && mSupportsPictureInPicture));
}
@@ -4058,11 +4098,18 @@ class Task extends WindowContainer<WindowContainer> {
info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
info.topActivityType = top.getActivityType();
info.isResizeable = isResizeable();
+ // Don't query getTopNonFinishingActivity().getBounds() directly because when fillTaskInfo
+ // is triggered for the first time after activities change, getBounds() may return non final
+ // bounds, e.g. fullscreen bounds instead of letterboxed bounds. To work around this,
+ // assigning bounds from ActivityRecord#layoutLetterbox when they are ready.
+ info.letterboxActivityBounds = Rect.copyOrNull(mLetterboxActivityBounds);
+ info.positionInParent = getRelativePosition();
info.pictureInPictureParams = getPictureInPictureParams();
info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info
: null;
+ info.addLaunchCookie(mLaunchCookie);
forAllActivities(r -> {
info.addLaunchCookie(r.mLaunchCookie);
});
@@ -4076,6 +4123,21 @@ class Task extends WindowContainer<WindowContainer> {
? null : rootActivity.pictureInPictureArgs;
}
+ void maybeUpdateLetterboxBounds(
+ ActivityRecord activityRecord, @Nullable Rect letterboxActivityBounds) {
+ if (isOrganized()
+ && mReuseActivitiesReport.top == activityRecord
+ // Want to force update only if letterbox bounds have changed.
+ && !Objects.equals(
+ mLetterboxActivityBounds,
+ letterboxActivityBounds)) {
+ mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds);
+ // Forcing update to reduce visual jank during the transition.
+ mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
+ this, /* force= */ true);
+ }
+ }
+
/**
* Returns a {@link TaskInfo} with information from this task.
*/
@@ -4817,6 +4879,13 @@ class Task extends WindowContainer<WindowContainer> {
return mHasBeenVisible;
}
+ void setDeferTaskAppear(boolean deferTaskAppear) {
+ mDeferTaskAppear = deferTaskAppear;
+ if (!mDeferTaskAppear) {
+ sendTaskAppeared();
+ }
+ }
+
/** In the case that these conditions are true, we want to send the Task to the organizer:
* 1. An organizer has been set
* 2. The Task was created by the organizer
@@ -4831,6 +4900,10 @@ class Task extends WindowContainer<WindowContainer> {
return false;
}
+ if (mDeferTaskAppear) {
+ return false;
+ }
+
if (mCreatedByOrganizer) {
return true;
}
@@ -5264,14 +5337,12 @@ class Task extends WindowContainer<WindowContainer> {
taskDisplayArea.moveHomeStackToFront(reason + " returnToHome");
}
- if (isRootTask()) {
- taskDisplayArea.positionChildAt(POSITION_TOP, this, false /* includingParents */,
- reason);
- }
+ final Task lastFocusedTask = isRootTask() ? taskDisplayArea.getFocusedStack() : null;
if (task == null) {
task = this;
}
task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */);
+ taskDisplayArea.updateLastFocusedRootTask(lastFocusedTask, reason);
}
/**
@@ -5294,8 +5365,9 @@ class Task extends WindowContainer<WindowContainer> {
if (parentTask != null) {
parentTask.moveToBack(reason, this);
} else {
- displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/,
- reason);
+ final Task lastFocusedTask = displayArea.getFocusedStack();
+ displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/);
+ displayArea.updateLastFocusedRootTask(lastFocusedTask, reason);
}
if (task != null && task != this) {
positionChildAtBottom(task);
@@ -5886,6 +5958,8 @@ class Task extends WindowContainer<WindowContainer> {
if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.getWindowingMode() != WINDOWING_MODE_FREEFORM
&& taskDisplayArea.allResumedActivitiesComplete()) {
+ // The activity may be waiting for stop, but that is no longer appropriate for it.
+ mStackSupervisor.mStoppingActivities.remove(next);
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
@@ -6804,13 +6878,10 @@ class Task extends WindowContainer<WindowContainer> {
// get calculated incorrectly.
mDisplayContent.deferUpdateImeTarget();
- // Shift all activities with this task up to the top
- // of the stack, keeping them in the same internal order.
- positionChildAtTop(tr);
-
// Don't refocus if invisible to current user
final ActivityRecord top = tr.getTopNonFinishingActivity();
if (top == null || !top.okToShowLocked()) {
+ positionChildAtTop(tr);
if (top != null) {
mStackSupervisor.mRecentTasks.add(top.getTask());
}
@@ -6818,20 +6889,15 @@ class Task extends WindowContainer<WindowContainer> {
return;
}
- // Set focus to the top running activity of this stack.
- final ActivityRecord r = topRunningActivity();
- if (r != null) {
- r.moveFocusableActivityToTop(reason);
- }
+ // Set focus to the top running activity of this task and move all its parents to top.
+ top.moveFocusableActivityToTop(reason);
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
if (noAnimation) {
mDisplayContent.prepareAppTransitionOld(TRANSIT_OLD_NONE,
false /* alwaysKeepCurrent */);
mDisplayContent.prepareAppTransition(TRANSIT_NONE);
- if (r != null) {
- mStackSupervisor.mNoAnimActivities.add(r);
- }
+ mStackSupervisor.mNoAnimActivities.add(top);
ActivityOptions.abort(options);
} else {
updateTransitLocked(TRANSIT_OLD_TASK_TO_FRONT, TRANSIT_TO_FRONT,
@@ -7170,7 +7236,7 @@ class Task extends WindowContainer<WindowContainer> {
ActivityRecord source, ActivityOptions options) {
Task task;
- if (DisplayContent.alwaysCreateStack(getWindowingMode(), getActivityType())) {
+ if (DisplayContent.canReuseExistingTask(getWindowingMode(), getActivityType())) {
// This stack will only contain one task, so just return itself since all stacks ara now
// tasks and all tasks are now stacks.
task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity);
@@ -7435,6 +7501,12 @@ class Task extends WindowContainer<WindowContainer> {
outPos.y -= outset;
}
+ private Point getRelativePosition() {
+ Point position = new Point();
+ getRelativePosition(position);
+ return position;
+ }
+
boolean shouldIgnoreInput() {
if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) {
return true;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9392666fbf54..e7213192dfd3 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -50,6 +50,7 @@ import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
import android.os.UserHandle;
import android.util.IntArray;
import android.util.Slog;
@@ -334,29 +335,6 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return true;
}
- void positionChildAt(int position, Task child, boolean includingParents,
- String updateLastFocusedTaskReason) {
- final Task prevFocusedTask = updateLastFocusedTaskReason != null ? getFocusedStack() : null;
-
- positionChildAt(position, child, includingParents);
-
- if (updateLastFocusedTaskReason == null) {
- return;
- }
-
- final Task currentFocusedStack = getFocusedStack();
- if (currentFocusedStack == prevFocusedTask) {
- return;
- }
-
- mLastFocusedStack = prevFocusedTask;
- EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser,
- mDisplayContent.mDisplayId,
- currentFocusedStack == null ? -1 : currentFocusedStack.getRootTaskId(),
- mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
- updateLastFocusedTaskReason);
- }
-
@Override
void positionChildAt(int position, Task child, boolean includingParents) {
final boolean moveToTop = position >= getChildCount() - 1;
@@ -996,6 +974,13 @@ final class TaskDisplayArea extends DisplayArea<Task> {
false /* createdByOrganizer */);
}
+ Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
+ Intent intent, boolean createdByOrganizer) {
+ return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */,
+ false /* createdByOrganizer */ , false /* deferTaskAppear */,
+ null /* launchCookie */);
+ }
+
/**
* Creates a stack matching the input windowing mode and activity type on this display.
*
@@ -1013,10 +998,14 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* @param intent The intent that started this task.
* @param createdByOrganizer @{code true} if this is created by task organizer, @{code false}
* otherwise.
+ * @param deferTaskAppear @{code true} if the task appeared signal should be deferred.
+ * @param launchCookie Launch cookie used for tracking/association of the task we are
+ * creating.
* @return The newly created stack.
*/
Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
- Intent intent, boolean createdByOrganizer) {
+ Intent intent, boolean createdByOrganizer, boolean deferTaskAppear,
+ IBinder launchCookie) {
if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) {
// Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
// anything else should be passing it in anyways...except for the task organizer.
@@ -1048,7 +1037,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
final int stackId = getNextStackId();
return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent,
- createdByOrganizer);
+ createdByOrganizer, deferTaskAppear, launchCookie);
}
/** @return the root task to create the next task in. */
@@ -1078,8 +1067,9 @@ final class TaskDisplayArea extends DisplayArea<Task> {
}
@VisibleForTesting
- Task createStackUnchecked(int windowingMode, int activityType, int stackId,
- boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+ Task createStackUnchecked(int windowingMode, int activityType, int stackId, boolean onTop,
+ ActivityInfo info, Intent intent, boolean createdByOrganizer, boolean deferTaskAppear,
+ IBinder launchCookie) {
if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
+ "activity type.");
@@ -1097,7 +1087,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
}
final Task stack = new Task(mAtmService, stackId, activityType,
- info, intent, createdByOrganizer);
+ info, intent, createdByOrganizer, deferTaskAppear, launchCookie);
if (launchRootTask != null) {
launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
if (onTop) {
@@ -1189,6 +1179,24 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return mLastFocusedStack;
}
+ void updateLastFocusedRootTask(Task prevFocusedTask, String updateLastFocusedTaskReason) {
+ if (updateLastFocusedTaskReason == null) {
+ return;
+ }
+
+ final Task currentFocusedTask = getFocusedStack();
+ if (currentFocusedTask == prevFocusedTask) {
+ return;
+ }
+
+ mLastFocusedStack = prevFocusedTask;
+ EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser,
+ mDisplayContent.mDisplayId,
+ currentFocusedTask == null ? -1 : currentFocusedTask.getRootTaskId(),
+ mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
+ updateLastFocusedTaskReason);
+ }
+
boolean allResumedActivitiesComplete() {
for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 6504f00905e7..a70efbcf5500 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -53,6 +53,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Consumer;
@@ -155,9 +156,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) {
- if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
- // Skip if the task has not yet received taskAppeared(), except for tasks created
- // by the organizer that don't receive that signal
+ if (!task.mTaskAppearedSent) {
+ // Skip if the task has not yet received taskAppeared().
return;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task info changed taskId=%d", task.mTaskId);
@@ -178,9 +178,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
void onBackPressedOnTaskRoot(Task task) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task back pressed on root taskId=%d",
task.mTaskId);
- if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
- // Skip if the task has not yet received taskAppeared(), except for tasks created
- // by the organizer that don't receive that signal
+ if (!task.mTaskAppearedSent) {
+ // Skip if the task has not yet received taskAppeared().
return;
}
if (!task.isOrganized()) {
@@ -401,30 +400,39 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
@Override
- public RunningTaskInfo createRootTask(int displayId, int windowingMode) {
+ public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) {
enforceStackPermission("createRootTask()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId);
if (display == null) {
- return null;
+ ProtoLog.e(WM_DEBUG_WINDOW_ORGANIZER,
+ "createRootTask unknown displayId=%d", displayId);
+ return;
}
- ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
- displayId, windowingMode);
- final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode,
- ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
- true /* createdByOrganizer */);
- RunningTaskInfo out = task.getTaskInfo();
- mLastSentTaskInfos.put(task, out);
- return out;
+ createRootTask(display, windowingMode, launchCookie);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ @VisibleForTesting
+ Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
+ display.mDisplayId, windowingMode);
+ // We want to defer the task appear signal until the task is fully created and attached to
+ // to the hierarchy so that the complete starting configuration is in the task info we send
+ // over to the organizer.
+ final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode,
+ ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
+ true /* createdByOrganizer */, true /* deferTaskAppear */, launchCookie);
+ task.setDeferTaskAppear(false /* deferTaskAppear */);
+ return task;
+ }
+
@Override
public boolean deleteRootTask(WindowContainerToken token) {
enforceStackPermission("deleteRootTask()");
@@ -475,6 +483,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
boolean changed = lastInfo == null
|| mTmpTaskInfo.topActivityType != lastInfo.topActivityType
|| mTmpTaskInfo.isResizeable != lastInfo.isResizeable
+ || !Objects.equals(
+ mTmpTaskInfo.letterboxActivityBounds,
+ lastInfo.letterboxActivityBounds)
+ || !Objects.equals(
+ mTmpTaskInfo.positionInParent,
+ lastInfo.positionInParent)
|| mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
|| mTmpTaskInfo.getConfiguration().windowConfiguration.getWindowingMode()
!= lastInfo.getConfiguration().windowConfiguration.getWindowingMode()
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ddda392ea951..411d8d60b7c4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1649,6 +1649,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
/**
+ * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+ * If an {@code adminComponent} is specified, then the caller must be an admin and
+ * the provided component name must match the caller's UID.
+ *
+ * If a package name is provided, then the caller doesn't have to be an admin, and the
+ * provided package must belong to the caller's UID.
+ *
+ * If neither is provided, the caller identity is returned as-is.
+ *
+ * Note: this method should only be called when the caller may not be an admin. If the caller
+ * is not an admin, the ComponentName in the returned identity will be null.
+ */
+ private CallerIdentity getNonPrivilegedOrAdminCallerIdentity(
+ @Nullable ComponentName adminComponent,
+ @Nullable String callerPackage) {
+ if (adminComponent != null) {
+ return getCallerIdentity(adminComponent);
+ }
+
+ return getNonPrivilegedOrAdminCallerIdentityUsingPackage(callerPackage);
+ }
+
+ /**
* Retrieves the active admin of the caller. This method should not be called directly and
* should only be called by {@link #getAdminCallerIdentity},
* {@link #getNonPrivilegedOrAdminCallerIdentity}, {@link #getAdminCallerIdentityUsingPackage}
@@ -2315,14 +2338,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final boolean isDeviceOwner = isDeviceOwner(admin.info.getComponent(), userId);
final boolean isProfileOwner = isProfileOwner(admin.info.getComponent(), userId);
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- throw new SecurityException("Admin " + admin.info.getComponent()
- + " does not own the device");
- }
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- throw new SecurityException("Admin " + admin.info.getComponent()
- + " does not own the profile");
- }
if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) {
throw new SecurityException("Admin " + admin.info.getComponent()
+ " is not a device owner or profile owner, so may not use policy: "
@@ -2428,20 +2443,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
ensureLocked();
final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
- final boolean ownsProfileOnOrganizationOwnedDevice =
- isProfileOwnerOfOrganizationOwnedDevice(admin.info.getComponent(), userId);
-
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- return ownsDevice;
- } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- // DO always has the PO power.
- return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile;
- } else {
- boolean allowedToUsePolicy = ownsDevice || ownsProfile
- || !DA_DISALLOWED_POLICIES.contains(reqPolicy)
- || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q;
- return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy);
- }
+
+ boolean allowedToUsePolicy = ownsDevice || ownsProfile
+ || !DA_DISALLOWED_POLICIES.contains(reqPolicy)
+ || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q;
+ return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy);
}
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -5488,22 +5494,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public List<String> getDelegatedScopes(ComponentName who,
String delegatePackage) throws SecurityException {
Objects.requireNonNull(delegatePackage, "Delegate package is null");
+ final CallerIdentity caller = getNonPrivilegedOrAdminCallerIdentity(who, delegatePackage);
- // Retrieve the user ID of the calling process.
- final int callingUid = mInjector.binderGetCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
+ // Ensure the caller may call this method:
+ // * Either it's an admin
+ // * Or it's an app identified by its calling package name (the
+ // getNonPrivilegedOrAdminCallerIdentity method validated the UID and package match).
+ Preconditions.checkCallAuthorization(
+ (caller.hasAdminComponent() && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || delegatePackage != null);
synchronized (getLockObject()) {
- // Ensure calling process is device/profile owner.
- if (who != null) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- // Or ensure calling process is delegatePackage itself.
- } else {
- if (!isCallingFromPackage(delegatePackage, callingUid)) {
- throw new SecurityException("Caller with uid " + callingUid + " is not "
- + delegatePackage);
- }
- }
- final DevicePolicyData policy = getUserData(userId);
+ final DevicePolicyData policy = getUserData(caller.getUserId());
// Retrieve the scopes assigned to delegatePackage, or null if no scope was given.
final List<String> scopes = policy.mDelegationMap.get(delegatePackage);
return scopes == null ? Collections.EMPTY_LIST : scopes;
@@ -7893,7 +7894,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// Current user has a managed-profile, but current user is not managed, so
// rather than moving to finalized state, go back to unmanaged once
// profile provisioning is complete.
- if (newState == DevicePolicyManager.STATE_USER_PROFILE_FINALIZED) {
+ if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
return;
}
break;
@@ -8362,37 +8363,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
private void enforceDeviceOwnerOrManageUsers() {
- synchronized (getLockObject()) {
- if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
- mInjector.binderGetCallingUid()) != null) {
- return;
- }
+ final CallerIdentity caller = getCallerIdentity();
+ if (isDeviceOwner(caller)) {
+ return;
}
- Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
}
private void enforceProfileOwnerOrSystemUser() {
- synchronized (getLockObject()) {
- if (getActiveAdminWithPolicyForUidLocked(null,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
- != null) {
- return;
- }
+ final CallerIdentity caller = getCallerIdentity();
+ if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ return;
}
- Preconditions.checkState(isCallerWithSystemUid(),
+ Preconditions.checkState(isSystemUid(caller),
"Only profile owner, device owner and system may call this method.");
}
private void enforceProfileOwnerOrFullCrossUsersPermission(CallerIdentity caller,
int userId) {
- if (userId == caller.getUserId()) {
- synchronized (getLockObject()) {
- if (getActiveAdminWithPolicyForUidLocked(null,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, caller.getUid()) != null) {
- // Device Owner/Profile Owner may access the user it runs on.
- return;
- }
- }
+ if ((userId == caller.getUserId()) && (isProfileOwner(caller) || isDeviceOwner(caller))) {
+ // Device Owner/Profile Owner may access the user it runs on.
+ return;
}
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
}
@@ -9285,10 +9276,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
throw new IllegalArgumentException("profileOwner " + profileOwner + " and admin "
+ admin + " are not in the same package");
}
+ final CallerIdentity caller = getCallerIdentity(admin);
// Only allow the system user to use this method
- if (!mInjector.binderGetCallingUserHandle().isSystem()) {
- throw new SecurityException("createAndManageUser was called from non-system user");
- }
+ Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
+ "createAndManageUser was called from non-system user");
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller));
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
@@ -9298,8 +9290,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// Create user.
UserHandle user = null;
synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
final int callingUid = mInjector.binderGetCallingUid();
final long id = mInjector.binderClearCallingIdentity();
try {
@@ -11161,25 +11151,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isActiveDeviceOwner(int uid) {
- synchronized (getLockObject()) {
- return getActiveAdminWithPolicyForUidLocked(
- null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, uid) != null;
- }
+ return isDeviceOwner(new CallerIdentity(uid, null, null));
}
@Override
public boolean isActiveProfileOwner(int uid) {
- synchronized (getLockObject()) {
- return getActiveAdminWithPolicyForUidLocked(
- null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid) != null;
- }
+ return isProfileOwner(new CallerIdentity(uid, null, null));
}
@Override
public boolean isActiveSupervisionApp(int uid) {
+ if (!isProfileOwner(new CallerIdentity(uid, null, null))) {
+ return false;
+ }
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked(
- null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid);
+ final ActiveAdmin admin = getProfileOwnerAdminLocked(UserHandle.getUserId(uid));
if (admin == null) {
return false;
}
@@ -11705,6 +11691,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.getPackageName();
try {
String[] pkgs = mInjector.getIPackageManager().getPackagesForUid(appUid);
+ if (pkgs == null) {
+ return false;
+ }
+
for (String pkg : pkgs) {
if (deviceOwnerPackageName.equals(pkg)) {
return true;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 975e2265b60c..e116a353c723 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -76,14 +76,18 @@ import android.provider.Settings;
import android.server.ServerProtoEnums;
import android.sysprop.VoldProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
+import android.util.TimeUtils;
import android.view.contentcapture.ContentCaptureManager;
import com.android.i18n.timezone.ZoneInfoDb;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
@@ -188,14 +192,20 @@ import dalvik.system.VMRuntime;
import com.google.android.startop.iorap.IorapForwardingService;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Timer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
-public final class SystemServer {
+/**
+ * Entry point to {@code system_server}.
+ */
+public final class SystemServer implements Dumpable {
private static final String TAG = "SystemServer";
@@ -384,6 +394,9 @@ public final class SystemServer {
private Future<?> mZygotePreload;
private Future<?> mBlobStoreServiceStart;
+ private final SystemServerDumper mDumper = new SystemServerDumper();
+
+
/**
* The pending WTF to be logged into dropbox.
*/
@@ -446,6 +459,75 @@ public final class SystemServer {
mRuntimeRestart = "1".equals(SystemProperties.get("sys.boot_completed"));
}
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ pw.printf("Runtime restart: %b\n", mRuntimeRestart);
+ pw.printf("Start count: %d\n", mStartCount);
+ pw.print("Runtime start-up time: ");
+ TimeUtils.formatDuration(mRuntimeStartUptime, pw); pw.println();
+ pw.print("Runtime start-elapsed time: ");
+ TimeUtils.formatDuration(mRuntimeStartElapsedTime, pw); pw.println();
+ }
+
+ private final class SystemServerDumper extends Binder {
+
+ @GuardedBy("mDumpables")
+ private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(4);
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final boolean hasArgs = args != null && args.length > 0;
+
+ synchronized (mDumpables) {
+ if (hasArgs && "--list".equals(args[0])) {
+ final int dumpablesSize = mDumpables.size();
+ for (int i = 0; i < dumpablesSize; i++) {
+ pw.println(mDumpables.keyAt(i));
+ }
+ return;
+ }
+
+ if (hasArgs && "--name".equals(args[0])) {
+ if (args.length < 2) {
+ pw.println("Must pass at least one argument to --name");
+ return;
+ }
+ final String name = args[1];
+ final Dumpable dumpable = mDumpables.get(name);
+ if (dumpable == null) {
+ pw.printf("No dummpable named %s\n", name);
+ return;
+ }
+
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ // Strip --name DUMPABLE from args
+ final String[] actualArgs = Arrays.copyOfRange(args, 2, args.length);
+ dumpable.dump(ipw, actualArgs);
+ }
+ return;
+ }
+
+ final int dumpablesSize = mDumpables.size();
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ for (int i = 0; i < dumpablesSize; i++) {
+ final Dumpable dumpable = mDumpables.valueAt(i);
+ ipw.printf("%s:\n", dumpable.getDumpableName());
+ ipw.increaseIndent();
+ dumpable.dump(ipw, args);
+ ipw.decreaseIndent();
+ ipw.println();
+ }
+ }
+ }
+ }
+
+ private void addDumpable(@NonNull Dumpable dumpable) {
+ synchronized (mDumpables) {
+ mDumpables.put(dumpable.getDumpableName(), dumpable);
+ }
+ }
+ }
+
private void run() {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
@@ -572,13 +654,21 @@ public final class SystemServer {
// Call per-process mainline module initialization.
ActivityThread.initializeMainlineModules();
+ // Sets the dumper service
+ ServiceManager.addService("system_server_dumper", mDumper);
+ mDumper.addDumpable(this);
+
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setStartInfo(mRuntimeRestart,
mRuntimeStartElapsedTime, mRuntimeStartUptime);
+ mDumper.addDumpable(mSystemServiceManager);
+
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
- SystemServerInitThreadPool.start();
+ SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
+ mDumper.addDumpable(tp);
+
// Attach JVMTI agent if this is a debuggable build and the system property is set.
if (Build.IS_DEBUGGABLE) {
// Property is of the form "library_path=parameters".
@@ -2321,7 +2411,11 @@ public final class SystemServer {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
t.traceBegin("StartCarServiceHelperService");
- mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
+ final SystemService cshs = mSystemServiceManager
+ .startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
+ if (cshs instanceof Dumpable) {
+ mDumper.addDumpable((Dumpable) cshs);
+ }
t.traceEnd();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index 29d3f29ad7e1..d7fef604d25b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -34,7 +34,6 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.listeners.ListenerExecutor.ListenerOperation;
import com.android.server.location.listeners.ListenerMultiplexer.UpdateServiceLock;
import org.junit.Before;
@@ -59,6 +58,9 @@ public class ListenerMultiplexerTest {
void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
+ void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
+
void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
@@ -90,8 +92,39 @@ public class ListenerMultiplexerTest {
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
+ mMultiplexer.addListener(1, consumer);
+ mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
+ any(TestListenerRegistration.class));
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
+ any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ assertThat(mMultiplexer.mRegistered).isTrue();
+ assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
+
+ mMultiplexer.notifyListeners();
+ verify(consumer).accept(any(TestListenerRegistration.class));
+ }
+
+ @Test
+ public void testReplace() {
+ Consumer<TestListenerRegistration> oldConsumer = mock(Consumer.class);
+ Consumer<TestListenerRegistration> consumer = mock(Consumer.class);
+
+ mMultiplexer.addListener(0, oldConsumer);
+ mInOrder.verify(mCallbacks).onRegister();
+ mInOrder.verify(mCallbacks).onRegistrationAdded(eq(oldConsumer),
+ any(TestListenerRegistration.class));
+ mInOrder.verify(mCallbacks).onActive();
+ mMultiplexer.replaceListener(1, oldConsumer, consumer);
+ mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
+ any(TestListenerRegistration.class));
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
+ any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ assertThat(mMultiplexer.mRegistered).isTrue();
+ assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
+
mMultiplexer.notifyListeners();
verify(consumer).accept(any(TestListenerRegistration.class));
+ verify(oldConsumer, never()).accept(any(TestListenerRegistration.class));
}
@Test
@@ -319,8 +352,7 @@ public class ListenerMultiplexerTest {
}
private static class TestListenerRegistration extends
- RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>,
- ListenerOperation<Consumer<TestListenerRegistration>>> {
+ RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
boolean mActive = true;
@@ -332,9 +364,7 @@ public class ListenerMultiplexerTest {
private static class TestMultiplexer extends
ListenerMultiplexer<Consumer<TestListenerRegistration>,
- Consumer<TestListenerRegistration>,
- ListenerOperation<Consumer<TestListenerRegistration>>, TestListenerRegistration,
- Integer> {
+ Consumer<TestListenerRegistration>, TestListenerRegistration, Integer> {
boolean mRegistered;
int mMergedRequest;
@@ -351,7 +381,13 @@ public class ListenerMultiplexerTest {
}
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
- addRegistration(consumer, new TestListenerRegistration(request, consumer));
+ putRegistration(consumer, new TestListenerRegistration(request, consumer));
+ }
+
+ public void replaceListener(Integer request, Consumer<TestListenerRegistration> oldConsumer,
+ Consumer<TestListenerRegistration> consumer) {
+ replaceRegistration(oldConsumer, consumer,
+ new TestListenerRegistration(request, consumer));
}
public void removeListener(Consumer<TestListenerRegistration> consumer) {
@@ -422,6 +458,13 @@ public class ListenerMultiplexerTest {
}
@Override
+ protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ TestListenerRegistration oldRegistration,
+ TestListenerRegistration newRegistration) {
+ mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
+ }
+
+ @Override
protected void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration) {
mCallbacks.onRegistrationRemoved(consumer, registration);
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index d2d85c860cd5..e76c5a476c48 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -46,7 +46,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
@@ -158,16 +157,16 @@ public class GestureLauncherServiceTest {
}
@Test
- public void testIsPanicButtonGestureEnabled_settingDisabled() {
- withPanicGestureEnabledSettingValue(false);
- assertFalse(mGestureLauncherService.isPanicButtonGestureEnabled(
+ public void testIsEmergencyGestureEnabled_settingDisabled() {
+ withEmergencyGestureEnabledSettingValue(false);
+ assertFalse(mGestureLauncherService.isEmergencyGestureEnabled(
mContext, FAKE_USER_ID));
}
@Test
- public void testIsPanicButtonGestureEnabled_settingEnabled() {
- withPanicGestureEnabledSettingValue(true);
- assertTrue(mGestureLauncherService.isPanicButtonGestureEnabled(
+ public void testIsEmergencyGestureEnabled_settingEnabled() {
+ withEmergencyGestureEnabledSettingValue(true);
+ assertTrue(mGestureLauncherService.isEmergencyGestureEnabled(
mContext, FAKE_USER_ID));
}
@@ -181,10 +180,10 @@ public class GestureLauncherServiceTest {
}
@Test
- public void testHandlePanicGesture_userSetupComplete() {
+ public void testHandleEmergencyGesture_userSetupComplete() {
withUserSetupCompleteValue(true);
- assertTrue(mGestureLauncherService.handlePanicButtonGesture());
+ assertTrue(mGestureLauncherService.handleEmergencyGesture());
}
@Test
@@ -196,10 +195,10 @@ public class GestureLauncherServiceTest {
}
@Test
- public void testHandlePanicGesture_userSetupNotComplete() {
+ public void testHandleEmergencyGesture_userSetupNotComplete() {
withUserSetupCompleteValue(false);
- assertFalse(mGestureLauncherService.handlePanicButtonGesture());
+ assertFalse(mGestureLauncherService.handleEmergencyGesture());
}
@Test
@@ -223,9 +222,9 @@ public class GestureLauncherServiceTest {
}
@Test
- public void testInterceptPowerKeyDown_firstPowerDown_panicGestureNotLaunched() {
- withPanicGestureEnabledSettingValue(true);
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ public void testInterceptPowerKeyDown_firstPowerDown_emergencyGestureNotLaunched() {
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS
+ GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS - 1;
@@ -425,12 +424,12 @@ public class GestureLauncherServiceTest {
@Test
public void
- testInterceptPowerKeyDown_fiveInboundPresses_cameraAndPanicEnabled_bothLaunch() {
+ testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() {
withCameraDoubleTapPowerEnableConfigValue(true);
withCameraDoubleTapPowerDisableSettingValue(0);
- withPanicGestureEnabledSettingValue(true);
+ withEmergencyGestureEnabledSettingValue(true);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ mGestureLauncherService.updateEmergencyGestureEnabled();
withUserSetupCompleteValue(true);
// First button press does nothing
@@ -476,7 +475,7 @@ public class GestureLauncherServiceTest {
assertEquals(1, tapCounts.get(0).intValue());
assertEquals(2, tapCounts.get(1).intValue());
- // Continue the button presses for the panic gesture.
+ // Continue the button presses for the emergency gesture.
// Presses 3 and 4 should not trigger any gesture
for (int i = 0; i < 2; i++) {
@@ -490,7 +489,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
}
- // Fifth button press should trigger the panic flow
+ // Fifth button press should trigger the emergency flow
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -513,9 +512,9 @@ public class GestureLauncherServiceTest {
@Test
public void
- testInterceptPowerKeyDown_fiveInboundPresses_panicGestureEnabled_launchesPanicFlow() {
- withPanicGestureEnabledSettingValue(true);
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ testInterceptPowerKeyDown_fiveInboundPresses_emergencyGestureEnabled_launchesFlow() {
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
withUserSetupCompleteValue(true);
// First button press does nothing
@@ -542,7 +541,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
}
- // Fifth button press should trigger the panic flow
+ // Fifth button press should trigger the emergency flow
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -565,9 +564,9 @@ public class GestureLauncherServiceTest {
@Test
public void
- testInterceptPowerKeyDown_tenInboundPresses_panicGestureEnabled_pressesIntercepted() {
- withPanicGestureEnabledSettingValue(true);
- mGestureLauncherService.updatePanicButtonGestureEnabled();
+ testInterceptPowerKeyDown_tenInboundPresses_emergencyGestureEnabled_keyIntercepted() {
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateEmergencyGestureEnabled();
withUserSetupCompleteValue(true);
// First button press does nothing
@@ -594,7 +593,7 @@ public class GestureLauncherServiceTest {
assertFalse(outLaunched.value);
}
- // Fifth button press should trigger the panic flow
+ // Fifth button press should trigger the emergency flow
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1128,10 +1127,10 @@ public class GestureLauncherServiceTest {
UserHandle.USER_CURRENT);
}
- private void withPanicGestureEnabledSettingValue(boolean enable) {
+ private void withEmergencyGestureEnabledSettingValue(boolean enable) {
Settings.Secure.putIntForUser(
mContentResolver,
- Settings.Secure.PANIC_GESTURE_ENABLED,
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
enable ? 1 : 0,
UserHandle.USER_CURRENT);
}
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 c4f7b9547277..30b1b3e78ad3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2814,7 +2814,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
exerciseUserProvisioningTransitions(CALLER_USER_HANDLE,
DevicePolicyManager.STATE_USER_PROFILE_COMPLETE,
- DevicePolicyManager.STATE_USER_PROFILE_FINALIZED);
+ DevicePolicyManager.STATE_USER_UNMANAGED);
}
public void testSetUserProvisioningState_managedProfileFromSetupWizard_managedProfile()
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 2a9c3942211c..8af7332e24ed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -114,7 +114,7 @@ public class ActiveSourceActionTest {
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
mPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 1385376b740d..37a75e3822aa 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -123,7 +123,7 @@ public class ArcInitiationActionFromAvrTest {
hdmiControlService.setCecController(hdmiCecController);
hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
- hdmiControlService.initPortInfo();
+ hdmiControlService.initService();
mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 169f885a7253..6027c3e4eeab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -117,7 +117,7 @@ public class ArcTerminationActionFromAvrTest {
hdmiControlService.setCecController(hdmiCecController);
hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
- hdmiControlService.initPortInfo();
+ hdmiControlService.initService();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 2c42791fabce..bb57a69d6f51 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -30,6 +30,7 @@ import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/** Fake {@link NativeWrapper} useful for testing. */
final class FakeNativeWrapper implements NativeWrapper {
@@ -55,6 +56,7 @@ final class FakeNativeWrapper implements NativeWrapper {
};
private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
+ private final Map<Integer, Boolean> mPortConnectionStatus = new HashMap<>();
private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>();
private int mMyPhysicalAddress = 0;
private HdmiPortInfo[] mHdmiPortInfo = null;
@@ -125,7 +127,12 @@ final class FakeNativeWrapper implements NativeWrapper {
@Override
public boolean nativeIsConnected(int port) {
- return false;
+ Boolean isConnected = mPortConnectionStatus.get(port);
+ return isConnected == null ? false : isConnected;
+ }
+
+ public void setPortConnectionStatus(int port, boolean connected) {
+ mPortConnectionStatus.put(port, connected);
}
public void onCecMessage(HdmiCecMessage hdmiCecMessage) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 74fd6830de61..433f6e7938e2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -15,13 +15,9 @@
*/
package com.android.server.hdmi;
-import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
-import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE;
-
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
-import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS;
@@ -33,6 +29,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioManager;
import android.os.Handler;
import android.os.IPowerManager;
@@ -226,7 +223,7 @@ public class HdmiCecLocalDeviceAudioSystemTest {
new HdmiPortInfo(
4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
// No TV device interacts with AVR so system audio control won't be turned on here
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -654,75 +651,6 @@ public class HdmiCecLocalDeviceAudioSystemTest {
}
@Test
- public void updateCecDevice_deviceNotExists_addDevice() {
- assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
- HdmiDeviceInfo newDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
-
- mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice);
- assertThat(mDeviceInfo).isEqualTo(newDevice);
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
- }
-
- @Test
- public void updateCecDevice_deviceExists_doNothing() {
- mInvokeDeviceEventState = 0;
- HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
- mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
-
- mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(0);
- }
-
- @Test
- public void updateCecDevice_deviceInfoDifferent_updateDevice() {
- assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
- HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
- mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
-
- HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2300, 4, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
-
- mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice);
- assertThat(mDeviceInfo).isEqualTo(differentDevice);
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
- }
-
- @Test
- @Ignore("b/120845532")
- public void handleReportPhysicalAddress_differentPath_addDevice() {
- assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
- HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
- mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
-
- HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
- ADDR_PLAYBACK_2, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_2));
- HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
- .buildReportPhysicalAddressCommand(
- ADDR_PLAYBACK_2, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK);
- mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress);
-
- mTestLooper.dispatchAll();
- assertThat(mDeviceInfo).isEqualTo(differentDevice);
- assertThat(mHdmiCecLocalDeviceAudioSystem
- .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
- assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
- }
-
- @Test
public void doNotWakeUpOnHotPlug_PlugIn() {
mWokenUp = false;
mHdmiCecLocalDeviceAudioSystem.onHotplug(0, true);
@@ -907,4 +835,42 @@ public class HdmiCecLocalDeviceAudioSystemTest {
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
}
+
+ @Test
+ @Ignore("b/151150320")
+ public void oneTouchPlay() {
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn_fromPlayback = HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+ HdmiCecMessage activeSource_fromPlayback = HdmiCecMessageBuilder.buildActiveSource(
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
+ SELF_PHYSICAL_ADDRESS);
+ HdmiCecMessage systemAudioModeRequest_fromPlayback =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
+ ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true);
+ HdmiCecMessage textViewOn_fromAudioSystem = HdmiCecMessageBuilder.buildTextViewOn(
+ mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+ HdmiCecMessage activeSource_fromAudioSystem = HdmiCecMessageBuilder.buildActiveSource(
+ mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
+ SELF_PHYSICAL_ADDRESS);
+ HdmiCecMessage systemAudioModeRequest_fromAudioSystem =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
+ ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn_fromPlayback);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource_fromPlayback);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+ systemAudioModeRequest_fromPlayback);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn_fromAudioSystem);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource_fromAudioSystem);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+ systemAudioModeRequest_fromAudioSystem);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index ef98b98ba7e1..440befcb3368 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -25,6 +25,8 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
@@ -125,7 +127,12 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
- mHdmiControlService.initPortInfo();
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mPlaybackPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
@@ -892,6 +899,7 @@ public class HdmiCecLocalDevicePlaybackTest {
public void handleSetStreamPath_afterHotplug_broadcastsActiveSource() {
mNativeWrapper.onHotplugEvent(1, false);
mNativeWrapper.onHotplugEvent(1, true);
+ mTestLooper.dispatchAll();
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
@@ -967,4 +975,73 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
+
+ @Test
+ public void oneTouchPlay_SendStandbyOnSleepToTv() {
+ mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
+ }
+
+ @Test
+ public void oneTouchPlay_SendStandbyOnSleepBroadcast() {
+ mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).contains(systemAudioModeRequest);
+ }
+
+ @Test
+ public void oneTouchPlay_SendStandbyOnSleepNone() {
+ mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
+ Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.SEND_STANDBY_ON_SLEEP_NONE);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress,
+ ADDR_TV);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+ HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index ce1cdf369076..bf4851b927b1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Handler;
import android.os.IPowerManager;
@@ -103,7 +104,11 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
- mHdmiControlService.initPortInfo();
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTvPhysicalAddress = 0x0000;
mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
@@ -119,8 +124,9 @@ public class HdmiCecLocalDeviceTvTest {
@Test
public void onAddressAllocated_invokesDeviceDiscovery() {
+ mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
- mHdmiCecLocalDeviceTv.onAddressAllocated(0, HdmiControlService.INITIATED_BY_BOOT_UP);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
new file mode 100644
index 000000000000..080b52bbbc6a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link HdmiCecNetwork} class.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class HdmiCecNetworkTest {
+
+ private HdmiCecNetwork mHdmiCecNetwork;
+
+ private Context mContext;
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiMhlControllerStub mHdmiMhlControllerStub;
+
+ private HdmiCecController mHdmiCecController;
+ private FakeNativeWrapper mNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+ private HdmiPortInfo[] mHdmiPortInfo;
+ private List<Integer> mDeviceEventListenerStatuses = new ArrayList<>();
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mHdmiControlService = new HdmiControlService(mContext) {
+ @Override
+ void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
+ mDeviceEventListenerStatuses.add(status);
+ }
+ };
+
+ mMyLooper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(mMyLooper);
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(mHdmiControlService,
+ mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService);
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub);
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+
+ mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService,
+ mHdmiCecController, mHdmiMhlControllerStub);
+
+ mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork);
+
+ mHdmiPortInfo = new HdmiPortInfo[5];
+ mHdmiPortInfo[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
+ mHdmiPortInfo[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
+ mHdmiPortInfo[2] =
+ new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ mHdmiPortInfo[3] =
+ new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
+ mHdmiPortInfo[4] =
+ new HdmiPortInfo(5, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(mHdmiPortInfo);
+ mHdmiCecNetwork.initPortInfo();
+ }
+
+ @Test
+ public void initializeNetwork_verifyPortInfo() {
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.getPortInfo()).hasSize(mHdmiPortInfo.length);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathExists_weAreNonTv() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(1);
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2234)).isEqualTo(2);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathExists_weAreSourceDevice() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x0000)).isEqualTo(5);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathExists_weAreTv() {
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(3);
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x3234)).isEqualTo(4);
+ }
+
+ @Test
+ public void physicalAddressToPort_pathInvalid() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ mHdmiCecNetwork.initPortInfo();
+ assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x1000)).isEqualTo(
+ Constants.INVALID_PORT_ID);
+ }
+
+ @Test
+ public void localDevices_verifyOne_tv() {
+ mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
+ new HdmiCecLocalDeviceTv(mHdmiControlService));
+
+ assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+ assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+ HdmiCecLocalDeviceTv.class);
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNotNull();
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNull();
+ }
+
+ @Test
+ public void localDevices_verifyOne_playback() {
+ mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ new HdmiCecLocalDevicePlayback(mHdmiControlService));
+
+ assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+ assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+ HdmiCecLocalDevicePlayback.class);
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNotNull();
+ assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNull();
+ }
+
+ @Test
+ public void cecDevices_tracking_logicalAddressOnly() throws Exception {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_logicalAddressOnly_doesntNotifyAgain() throws Exception {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportPhysicalAddress() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ physicalAddress);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_updateDeviceInfo_sameDoesntNotify() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+
+
+ // ADD for logical address first detected
+ // UPDATE for updating device with physical address
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportPowerStatus() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int powerStatus = HdmiControlManager.POWER_STATUS_ON;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportOsdName() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ String osdName = "Test Device";
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, osdName));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_reportVendorId() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int vendorId = 1234;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesDeviceInfo() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int physicalAddress = 0x1000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+ int powerStatus = HdmiControlManager.POWER_STATUS_ON;
+ String osdName = "Test Device";
+ int vendorId = 1234;
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ physicalAddress, type));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, osdName));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(physicalAddress);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName);
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesPhysicalAddress() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int initialPhysicalAddress = 0x1000;
+ int updatedPhysicalAddress = 0x2000;
+ int type = HdmiDeviceInfo.DEVICE_PLAYBACK;
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ initialPhysicalAddress, type));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress,
+ updatedPhysicalAddress, type));
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type);
+
+ // ADD for logical address first detected
+ // UPDATE for updating device with physical address
+ // UPDATE for updating device with new physical address
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesPowerStatus() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int powerStatus = HdmiControlManager.POWER_STATUS_ON;
+ int updatedPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, powerStatus));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress,
+ Constants.ADDR_BROADCAST, updatedPowerStatus));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(updatedPowerStatus);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesOsdName() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ String osdName = "Test Device";
+ String updatedOsdName = "Different";
+
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, osdName));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress,
+ Constants.ADDR_BROADCAST, updatedOsdName));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(updatedOsdName);
+ }
+
+ @Test
+ public void cecDevices_tracking_updatesVendorId() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ int vendorId = 1234;
+ int updatedVendorId = 12345;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId));
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, updatedVendorId));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress);
+ assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress);
+ assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED);
+ assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(
+ HdmiUtils.getDefaultDeviceName(logicalAddress));
+ assertThat(cecDeviceInfo.getVendorId()).isEqualTo(updatedVendorId);
+ assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ }
+
+ @Test
+ public void cecDevices_tracking_clearDevices() {
+ int logicalAddress = Constants.ADDR_PLAYBACK_1;
+ mHdmiCecNetwork.handleCecMessage(
+ HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000));
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
+
+ mHdmiCecNetwork.clearDeviceList();
+
+ assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).isEmpty();
+
+ assertThat(mDeviceEventListenerStatuses).containsExactly(
+ HdmiControlManager.DEVICE_EVENT_ADD_DEVICE,
+ HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
index c4068d34c00d..74a00521d7f4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
@@ -21,11 +21,13 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
+import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.Looper;
import android.os.test.TestLooper;
+import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -44,6 +46,8 @@ import java.util.ArrayList;
@RunWith(JUnit4.class)
public class HdmiControlServiceBinderAPITest {
+ private Context mContext;
+
private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice {
private boolean mCanGoToStandby;
@@ -111,8 +115,12 @@ public class HdmiControlServiceBinderAPITest {
@Before
public void SetUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ // Some tests expect no logical addresses being allocated at the beginning of the test.
+ setHdmiControlEnabled(false);
+
mHdmiControlService =
- new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ new HdmiControlService(mContext) {
@Override
void sendCecCommand(HdmiCecMessage command) {
switch (command.getOpcode()) {
@@ -164,7 +172,7 @@ public class HdmiControlServiceBinderAPITest {
mHdmiPortInfo[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
mResult = -1;
mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
@@ -183,6 +191,7 @@ public class HdmiControlServiceBinderAPITest {
assertEquals(mResult, -1);
assertThat(mPlaybackDevice.isActiveSource()).isFalse();
+ setHdmiControlEnabled(true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
@@ -192,6 +201,8 @@ public class HdmiControlServiceBinderAPITest {
@Test
public void oneTouchPlay_addressAllocated() {
+ setHdmiControlEnabled(true);
+
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
@@ -204,4 +215,10 @@ public class HdmiControlServiceBinderAPITest {
assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
assertThat(mPlaybackDevice.isActiveSource()).isTrue();
}
+
+ private void setHdmiControlEnabled(boolean enabled) {
+ int value = enabled ? 1 : 0;
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED,
+ value);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 2f48b5ee4c70..8f631a307f15 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -176,7 +176,7 @@ public class HdmiControlServiceTest {
mHdmiPortInfo[3] =
new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initPortInfo();
+ mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
@@ -206,29 +206,6 @@ public class HdmiControlServiceTest {
}
@Test
- public void pathToPort_pathExists_weAreNonTv() {
- mNativeWrapper.setPhysicalAddress(0x2000);
- mHdmiControlService.initPortInfo();
- assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1);
- assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2);
- }
-
- @Test
- public void pathToPort_pathExists_weAreTv() {
- mNativeWrapper.setPhysicalAddress(0x0000);
- mHdmiControlService.initPortInfo();
- assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3);
- assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4);
- }
-
- @Test
- public void pathToPort_pathInvalid() {
- mNativeWrapper.setPhysicalAddress(0x2000);
- mHdmiControlService.initPortInfo();
- assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID);
- }
-
- @Test
public void initialPowerStatus_normalBoot_isTransientToStandby() {
assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index 6be28d9a13be..0e4bfab0d94c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -90,8 +90,6 @@ public class SystemAudioInitiationActionFromAvrTest {
break;
case Constants.MESSAGE_INITIATE_ARC:
break;
- default:
- throw new IllegalArgumentException("Unexpected message");
}
}
@@ -159,6 +157,14 @@ public class SystemAudioInitiationActionFromAvrTest {
return -1;
}
};
+
+ Looper looper = mTestLooper.getLooper();
+ hdmiControlService.setIoLooper(looper);
+ HdmiCecController.NativeWrapper nativeWrapper = new FakeNativeWrapper();
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ hdmiControlService, nativeWrapper, hdmiControlService.getAtomWriter());
+ hdmiControlService.setCecController(hdmiCecController);
+ hdmiControlService.initService();
mHdmiCecLocalDeviceAudioSystem =
new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
@Override
@@ -181,8 +187,6 @@ public class SystemAudioInitiationActionFromAvrTest {
}
};
mHdmiCecLocalDeviceAudioSystem.init();
- Looper looper = mTestLooper.getLooper();
- hdmiControlService.setIoLooper(looper);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
index 86758f18a407..40d959d9793d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -222,4 +222,18 @@ public class IncrementalStatesTest {
assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
assertFalse(mIncrementalStates.isLoading());
}
+
+ /**
+ * Test startability transitions if app crashes or anrs
+ */
+ @Test
+ public void testStartableTransition_AppCrashOrAnr() {
+ mIncrementalStates.onCrashOrAnr();
+ assertFalse(mIncrementalStates.isStartable());
+ mIncrementalStates.setProgress(1.0f);
+ assertTrue(mIncrementalStates.isStartable());
+ mIncrementalStates.onCrashOrAnr();
+ // Test that if fully loaded, app remains startable even if it has crashed
+ assertTrue(mIncrementalStates.isStartable());
+ }
}
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 caf8a720e26c..b6323313dd27 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -245,9 +245,8 @@ public class ActivityStackTests extends WindowTestsBase {
.setStack(rootHomeTask)
.setCreateTask(true)
.build();
- final Task secondaryStack = (Task) WindowContainer.fromBinder(
- mAtm.mTaskOrganizerController.createRootTask(rootHomeTask.getDisplayId(),
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token.asBinder());
+ final Task secondaryStack = mAtm.mTaskOrganizerController.createRootTask(
+ rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
rootHomeTask.reparent(secondaryStack, POSITION_TOP);
assertEquals(secondaryStack, rootHomeTask.getParent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e47881917b2c..3720e520b1b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1022,7 +1022,7 @@ public class ActivityStarterTests extends WindowTestsBase {
assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
// Move activity to split-screen-primary stack and make sure it has the focus.
- TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayId());
+ TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM);
top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index cc37e2bf94ca..d68dde5734ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -750,17 +750,15 @@ public class SizeCompatTests extends WindowTestsBase {
final Rect taskBounds = mTask.getBounds();
final Rect newActivityBounds = newActivity.getBounds();
- // Task bounds should be 700x1400 with the ratio as the display.
+ // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
assertTrue(mTask.isTaskLetterboxed());
assertEquals(displayBounds.height(), taskBounds.height());
- assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
+ assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio),
taskBounds.width());
- // App bounds should be 700x(710 x 1.3 = 910)
+ // App bounds should be fullscreen in Task bounds.
assertFalse(newActivity.inSizeCompatMode());
- assertEquals(taskBounds.width(), newActivityBounds.width());
- assertEquals((long) Math.rint(taskBounds.width() * newActivity.info.maxAspectRatio),
- newActivityBounds.height());
+ assertEquals(taskBounds, newActivityBounds);
}
private static WindowState addWindowToActivity(ActivityRecord activity) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7a1f65a3b62c..b4480aea3ce4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -479,21 +479,22 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testCreateDeleteRootTasks() {
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
+
+ Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ dc, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+ RunningTaskInfo info1 = task1.getTaskInfo();
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
info1.configuration.windowConfiguration.getWindowingMode());
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
- RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- Display.DEFAULT_DISPLAY,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ dc, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info2 = task2.getTaskInfo();
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
info2.configuration.windowConfiguration.getWindowingMode());
assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType);
- DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
List<Task> infos = getTasksCreatedByOrganizer(dc);
assertEquals(2, infos.size());
@@ -521,8 +522,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info1 = task.getTaskInfo();
final Task stack = createTaskStackOnDisplay(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
@@ -579,8 +581,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info1 = task.getTaskInfo();
lastReportedTiles.clear();
called[0] = false;
@@ -640,10 +643,13 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
- RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
- mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+ Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+ RunningTaskInfo info1 = task1.getTaskInfo();
+ Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+ RunningTaskInfo info2 = task2.getTaskInfo();
final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */).size();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1eb7cbed02c8..62f04a1c830a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -973,8 +973,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
final int taskId = mTaskId >= 0 ? mTaskId : mTaskDisplayArea.getNextStackId();
if (mParentTask == null) {
task = mTaskDisplayArea.createStackUnchecked(
- mWindowingMode, mActivityType, taskId, mOnTop, mActivityInfo,
- mIntent, false /* createdByOrganizer */);
+ mWindowingMode, mActivityType, taskId, mOnTop, mActivityInfo, mIntent,
+ false /* createdByOrganizer */, false /* deferTaskAppear */,
+ null /* launchCookie */);
} else {
task = new Task(mSupervisor.mService, taskId, mActivityInfo,
mIntent /*intent*/, mVoiceSession, null /*_voiceInteractor*/,
@@ -1016,20 +1017,17 @@ class WindowTestsBase extends SystemServiceTestsBase {
// moves everything to secondary. Most tests expect this since sysui usually does it.
boolean mMoveToSecondaryOnEnter = true;
int mDisplayId;
- TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
+ TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) {
mService = service;
- mDisplayId = displayId;
+ mDisplayId = display.mDisplayId;
mService.mTaskOrganizerController.registerTaskOrganizer(this);
- WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask(
- displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
- mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
- WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask(
- displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
- mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask();
+ mPrimary = mService.mTaskOrganizerController.createRootTask(
+ display, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+ mSecondary = mService.mTaskOrganizerController.createRootTask(
+ display, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);;
}
TestSplitOrganizer(ActivityTaskManagerService service) {
- this(service,
- service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay().mDisplayId);
+ this(service, service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay());
}
public void setMoveToSecondaryOnEnter(boolean move) {
mMoveToSecondaryOnEnter = move;
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 63e0e5a36a26..a58b6d84cbd1 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -747,6 +747,7 @@ package android.telephony {
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
+ method public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -780,6 +781,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+ method public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
@@ -819,6 +821,11 @@ package android.telephony {
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2
+ field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -851,6 +858,9 @@ package android.telephony {
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
+ field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
+ field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 92238420fd32..f8a200a5f8d3 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -369,7 +369,6 @@ public final class NetworkRegistrationInfo implements Parcelable {
* Get the 5G NR connection state.
*
* @return the 5G NR connection state.
- * @hide
*/
public @NRState int getNrState() {
return mNrState;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 20eeb1da0d99..aa68dcf5a88e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13672,6 +13672,149 @@ public class TelephonyManager {
}
}
+ /**
+ * No error. Operation succeeded.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0;
+
+ /**
+ * NR Dual connectivity enablement is not supported.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1;
+
+ /**
+ * Radio is not available.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2;
+
+ /**
+ * Internal Radio error.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3;
+
+ /**
+ * Currently in invalid state. Not able to process the request.
+ * @hide
+ */
+ @SystemApi
+ public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ENABLE_NR_DUAL_CONNECTIVITY"}, value = {
+ ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS,
+ ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED,
+ ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE,
+ ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE,
+ ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR})
+ public @interface EnableNrDualConnectivityResult {}
+
+ /**
+ * Enable NR dual connectivity. Enabled state does not mean dual connectivity
+ * is active. It means device is allowed to connect to both primary and secondary.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1;
+
+ /**
+ * Disable NR dual connectivity. Disabled state does not mean the secondary cell is released.
+ * Modem will release it only if the current bearer is released to avoid radio link failure.
+ * @hide
+ */
+ @SystemApi
+ public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2;
+
+ /**
+ * Disable NR dual connectivity and force the secondary cell to be released if dual connectivity
+ * was active. This will result in radio link failure.
+ * @hide
+ */
+ @SystemApi
+ public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "NR_DUAL_CONNECTIVITY_" }, value = {
+ NR_DUAL_CONNECTIVITY_ENABLE,
+ NR_DUAL_CONNECTIVITY_DISABLE,
+ NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NrDualConnectivityState {
+ }
+
+ /**
+ * Enable/Disable E-UTRA-NR Dual Connectivity.
+ *
+ * @param nrDualConnectivityState expected NR dual connectivity state
+ * This can be passed following states
+ * <ol>
+ * <li>Enable NR dual connectivity {@link #NR_DUAL_CONNECTIVITY_ENABLE}
+ * <li>Disable NR dual connectivity {@link #NR_DUAL_CONNECTIVITY_DISABLE}
+ * <li>Disable NR dual connectivity and force secondary cell to be released
+ * {@link #NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE}
+ * </ol>
+ * @return operation result.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @SystemApi
+ public @EnableNrDualConnectivityResult int setNrDualConnectivityState(
+ @NrDualConnectivityState int nrDualConnectivityState) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.setNrDualConnectivityState(getSubId(), nrDualConnectivityState);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setNrDualConnectivityState RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+
+ return ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE;
+ }
+
+ /**
+ * Is E-UTRA-NR Dual Connectivity enabled.
+ * @return true if dual connectivity is enabled else false. Enabled state does not mean dual
+ * connectivity is active. It means the device is allowed to connect to both primary and
+ * secondary cell.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @SystemApi
+ public boolean isNrDualConnectivityEnabled() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isNrDualConnectivityEnabled(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "isNRDualConnectivityEnabled RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
private static class DeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0d8351d1ff12..d33835f27053 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2216,4 +2216,20 @@ interface ITelephony {
* does not exist on the SIM card.
*/
List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId);
+
+ /**
+ * Enable/Disable E-UTRA-NR Dual Connectivity
+ * @return operation result. See TelephonyManager.EnableNrDualConnectivityResult for
+ * details
+ * @param subId the id of the subscription
+ * @param enable enable/disable dual connectivity
+ */
+ int setNrDualConnectivityState(int subId, int nrDualConnectivityState);
+
+ /**
+ * Is E-UTRA-NR Dual Connectivity enabled
+ * @param subId the id of the subscription
+ * @return true if dual connectivity is enabled else false
+ */
+ boolean isNrDualConnectivityEnabled(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index d524299d7ede..953a2924dc44 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -494,6 +494,8 @@ public interface RILConstants {
int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
int RIL_REQUEST_GET_BARRING_INFO = 211;
int RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION = 212;
+ int RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY = 213;
+ int RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED = 214;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 992493143179..6cc2e2236127 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -100,10 +100,8 @@ class CloseImeAutoOpenWindowToHomeTest(
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
- navBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
- statusBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
imeLayerBecomesInvisible(bugId = 141458352)
imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 46f584b51e8c..8d9881ec2063 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -106,10 +106,8 @@ class CloseImeWindowToHomeTest(
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
- navBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
- statusBarLayerIsAlwaysVisible(configuration.startRotation !=
- Surface.ROTATION_0)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
imeLayerBecomesInvisible(bugId = 153739621)
imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 1194933ee315..ad23d9f48568 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -100,10 +100,8 @@ class OpenAppColdTest(
configuration.endRotation)
statusBarLayerRotatesScales(Surface.ROTATION_0,
configuration.endRotation)
- navBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
- statusBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
wallpaperLayerBecomesInvisible(testApp)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 136be29e421d..5886a61eed34 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -104,10 +104,8 @@ class OpenAppWarmTest(
configuration.endRotation)
statusBarLayerRotatesScales(Surface.ROTATION_0,
configuration.endRotation)
- navBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
- statusBarLayerIsAlwaysVisible(Surface.ROTATION_0 !=
- configuration.endRotation)
+ navBarLayerIsAlwaysVisible(enabled = false)
+ statusBarLayerIsAlwaysVisible(enabled = false)
wallpaperLayerBecomesInvisible(testApp)
}
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index da644021e30e..e4b96b1d4f5f 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -15,7 +15,7 @@
# limitations under the License.
"""Generate API lists for non-SDK API enforcement."""
import argparse
-from collections import defaultdict
+from collections import defaultdict, namedtuple
import functools
import os
import re
@@ -54,16 +54,21 @@ ALL_FLAGS = FLAGS_API_LIST + [
FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
ALL_FLAGS_SET = set(ALL_FLAGS)
-# Suffix used in command line args to express that only known and
-# otherwise unassigned entries should be assign the given flag.
+# Option specified after one of FLAGS_API_LIST to indicate that
+# only known and otherwise unassigned entries should be assign the
+# given flag.
# For example, the max-target-P list is checked in as it was in P,
# but signatures have changes since then. The flag instructs this
# script to skip any entries which do not exist any more.
-FLAG_IGNORE_CONFLICTS_SUFFIX = "-ignore-conflicts"
+FLAG_IGNORE_CONFLICTS = "ignore-conflicts"
-# Suffix used in command line args to express that all apis within a given set
-# of packages should be assign the given flag.
-FLAG_PACKAGES_SUFFIX = "-packages"
+# Option specified after one of FLAGS_API_LIST to express that all
+# apis within a given set of packages should be assign the given flag.
+FLAG_PACKAGES = "packages"
+
+# Option specified after one of FLAGS_API_LIST to indicate an extra
+# tag that should be added to the matching APIs.
+FLAG_TAG = "tag"
# Regex patterns of fields/methods used in serialization. These are
# considered public API despite being hidden.
@@ -86,6 +91,17 @@ HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersectio
IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
+class StoreOrderedOptions(argparse.Action):
+ """An argparse action that stores a number of option arguments in the order that
+ they were specified.
+ """
+ def __call__(self, parser, args, values, option_string = None):
+ items = getattr(args, self.dest, None)
+ if items is None:
+ items = []
+ items.append([option_string.lstrip('-'), values])
+ setattr(args, self.dest, items)
+
def get_args():
"""Parses command line arguments.
@@ -98,17 +114,19 @@ def get_args():
help='CSV files to be merged into output')
for flag in ALL_FLAGS:
- ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX
- packages_flag = flag + FLAG_PACKAGES_SUFFIX
- parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE',
- help='lists of entries with flag "' + flag + '"')
- parser.add_argument('--' + ignore_conflicts_flag, dest=ignore_conflicts_flag, nargs='*',
- default=[], metavar='TXT_FILE',
- help='lists of entries with flag "' + flag +
- '". skip entry if missing or flag conflict.')
- parser.add_argument('--' + packages_flag, dest=packages_flag, nargs='*',
- default=[], metavar='TXT_FILE',
- help='lists of packages to be added to ' + flag + ' list')
+ parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE',
+ action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"')
+ parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0,
+ action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned '
+ 'entries should be assign the given flag. Must follow a list of entries and applies '
+ 'to the preceding such list.')
+ parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0,
+ action=StoreOrderedOptions, help='Indicates that the previous list of entries '
+ 'is a list of packages. All members in those packages will be given the flag. '
+ 'Must follow a list of entries and applies to the preceding such list.')
+ parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1,
+ action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. '
+ 'Must follow a list of entries and applies to the preceding such list.')
return parser.parse_args()
@@ -258,7 +276,7 @@ class FlagsDict:
flags.append(FLAG_SDK)
self._dict[csv[0]].update(flags)
- def assign_flag(self, flag, apis, source="<unknown>"):
+ def assign_flag(self, flag, apis, source="<unknown>", tag = None):
"""Assigns a flag to given subset of entries.
Args:
@@ -278,11 +296,44 @@ class FlagsDict:
# Iterate over the API subset, find each entry in dict and assign the flag to it.
for api in apis:
self._dict[api].add(flag)
+ if tag:
+ self._dict[api].add(tag)
+
+
+FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
+
+def parse_ordered_flags(ordered_flags):
+ r = []
+ currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None
+ for flag_value in ordered_flags:
+ flag, value = flag_value[0], flag_value[1]
+ if flag in ALL_FLAGS_SET:
+ if currentflag:
+ r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+ ignore_conflicts, packages, tag = False, False, None
+ currentflag = flag
+ file = value
+ else:
+ if currentflag is None:
+ raise argparse.ArgumentError('--%s is only allowed after one of %s' % (
+ flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
+ if flag == FLAG_IGNORE_CONFLICTS:
+ ignore_conflicts = True
+ elif flag == FLAG_PACKAGES:
+ packages = True
+ elif flag == FLAG_TAG:
+ tag = value[0]
+
+
+ if currentflag:
+ r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+ return r
def main(argv):
# Parse arguments.
args = vars(get_args())
+ flagfiles = parse_ordered_flags(args['ordered_flags'])
# Initialize API->flags dictionary.
flags = FlagsDict()
@@ -300,28 +351,28 @@ def main(argv):
flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION))
# (2) Merge text files with a known flag into the dictionary.
- for flag in ALL_FLAGS:
- for filename in args[flag]:
- flags.assign_flag(flag, read_lines(filename), filename)
+ for info in flagfiles:
+ if (not info.ignore_conflicts) and (not info.packages):
+ flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag)
# Merge text files where conflicts should be ignored.
# This will only assign the given flag if:
# (a) the entry exists, and
# (b) it has not been assigned any other flag.
# Because of (b), this must run after all strict assignments have been performed.
- for flag in ALL_FLAGS:
- for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]:
- valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename))
- flags.assign_flag(flag, valid_entries, filename)
+ for info in flagfiles:
+ if info.ignore_conflicts:
+ valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file))
+ flags.assign_flag(info.flag, valid_entries, filename, info.tag)
# All members in the specified packages will be assigned the appropriate flag.
- for flag in ALL_FLAGS:
- for filename in args[flag + FLAG_PACKAGES_SUFFIX]:
- packages_needing_list = set(read_lines(filename))
+ for info in flagfiles:
+ if info.packages:
+ packages_needing_list = set(read_lines(info.file))
should_add_signature_to_list = lambda sig,lists: extract_package(
sig) in packages_needing_list and not lists
valid_entries = flags.filter_apis(should_add_signature_to_list)
- flags.assign_flag(flag, valid_entries)
+ flags.assign_flag(info.flag, valid_entries, info.file, info.tag)
# Mark all remaining entries as blocked.
flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))