summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp53
-rw-r--r--CleanSpec.mk2
-rw-r--r--apex/Android.bp3
-rw-r--r--apex/appsearch/Android.bp6
-rw-r--r--apex/appsearch/framework/Android.bp57
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java12
-rw-r--r--apex/statsd/aidl/Android.bp12
-rw-r--r--apex/statsd/aidl/android/os/IStatsManagerService.aidl65
-rw-r--r--apex/statsd/service/java/com/android/server/stats/StatsCompanion.java70
-rw-r--r--apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java40
-rw-r--r--apex/statsd/service/java/com/android/server/stats/StatsManagerService.java108
-rw-r--r--api/current.txt67
-rw-r--r--api/removed.txt21
-rwxr-xr-xapi/system-current.txt45
-rw-r--r--api/test-current.txt17
-rw-r--r--cmds/statsd/Android.bp58
-rw-r--r--cmds/statsd/src/FieldValue.cpp2
-rw-r--r--cmds/statsd/src/StatsLogProcessor.cpp1
-rw-r--r--cmds/statsd/src/external/PowerStatsPuller.cpp1
-rw-r--r--cmds/statsd/src/external/puller_util.cpp2
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.h2
-rw-r--r--cmds/statsd/src/metrics/MetricsManager.cpp1
-rw-r--r--cmds/statsd/src/metrics/metrics_manager_util.cpp2
-rw-r--r--cmds/statsd/src/state/StateTracker.h2
-rw-r--r--cmds/statsd/src/stats_log_util.h2
-rw-r--r--cmds/statsd/tests/external/GpuStatsPuller_test.cpp1
-rw-r--r--cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp1
-rw-r--r--cmds/statsd/tests/external/puller_util_test.cpp1
-rw-r--r--cmds/svc/src/com/android/commands/svc/DataCommand.java44
-rw-r--r--core/java/android/accessibilityservice/AccessibilityGestureEvent.java4
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java12
-rw-r--r--core/java/android/accounts/AccountManager.java29
-rw-r--r--core/java/android/accounts/IAccountManager.aidl3
-rw-r--r--core/java/android/app/ActivityManager.java126
-rw-r--r--core/java/android/app/ContextImpl.java21
-rw-r--r--core/java/android/app/DownloadManager.java4
-rw-r--r--core/java/android/app/StatsManager.java14
-rw-r--r--core/java/android/app/SystemServiceRegistry.java16
-rw-r--r--core/java/android/app/role/RoleControllerService.java15
-rw-r--r--core/java/android/app/role/RoleManager.java33
-rw-r--r--core/java/android/content/Context.java40
-rw-r--r--core/java/android/content/ContextWrapper.java10
-rw-r--r--core/java/android/content/pm/BaseParceledListSlice.java2
-rw-r--r--core/java/android/content/pm/DataLoaderParams.java (renamed from core/java/android/os/incremental/IncrementalDataLoaderParams.java)14
-rw-r--r--core/java/android/content/pm/DataLoaderParamsParcel.aidl (renamed from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl)6
-rw-r--r--core/java/android/content/pm/FileSystemControlParcel.aidl31
-rw-r--r--core/java/android/content/pm/IDataLoader.aidl1
-rw-r--r--core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl24
-rw-r--r--core/java/android/content/pm/NamedParcelFileDescriptor.aidl (renamed from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl)2
-rw-r--r--core/java/android/content/pm/PackageInstaller.java12
-rw-r--r--core/java/android/content/pm/PackageManager.java10
-rw-r--r--core/java/android/content/pm/parsing/AndroidPackage.java5
-rw-r--r--core/java/android/content/pm/parsing/ApkParseUtils.java21
-rw-r--r--core/java/android/content/pm/parsing/PackageImpl.java24
-rw-r--r--core/java/android/content/pm/parsing/ParsingPackage.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java1
-rw-r--r--core/java/android/hardware/soundtrigger/ConversionUtil.java329
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java176
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerModule.java304
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java18
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java185
-rw-r--r--core/java/android/os/Environment.java18
-rw-r--r--core/java/android/os/IBinder.java8
-rw-r--r--core/java/android/os/IVibratorService.aidl1
-rw-r--r--core/java/android/os/Parcel.java23
-rw-r--r--core/java/android/os/PowerManager.java69
-rw-r--r--core/java/android/os/Process.java15
-rw-r--r--core/java/android/os/SystemVibrator.java14
-rw-r--r--core/java/android/os/Vibrator.java19
-rw-r--r--core/java/android/os/ZygoteProcess.java35
-rw-r--r--core/java/android/os/incremental/IIncrementalManager.aidl8
-rw-r--r--core/java/android/os/incremental/IIncrementalManagerNative.aidl4
-rw-r--r--core/java/android/os/incremental/IncrementalFileStorages.java11
-rw-r--r--core/java/android/os/incremental/IncrementalManager.java3
-rw-r--r--core/java/android/os/storage/StorageManager.java35
-rw-r--r--core/java/android/os/storage/StorageVolume.java46
-rw-r--r--core/java/android/os/storage/VolumeRecord.java25
-rw-r--r--core/java/android/provider/BaseColumns.java4
-rw-r--r--core/java/android/provider/MediaStore.java905
-rw-r--r--core/java/android/provider/Settings.java52
-rw-r--r--core/java/android/service/autofill/FillRequest.java107
-rw-r--r--core/java/android/service/autofill/FillResponse.java40
-rw-r--r--core/java/android/service/dataloader/DataLoaderService.java307
-rw-r--r--core/java/android/service/incremental/IncrementalDataLoaderService.java563
-rw-r--r--core/java/android/service/notification/Adjustment.java7
-rw-r--r--core/java/android/telephony/PhoneStateListener.java84
-rw-r--r--core/java/android/telephony/TelephonyRegistryManager.java64
-rw-r--r--core/java/android/util/CloseGuard.java9
-rw-r--r--core/java/android/view/FrameMetricsObserver.java61
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java36
-rw-r--r--core/java/android/view/InsetsController.java9
-rw-r--r--core/java/android/view/View.java14
-rw-r--r--core/java/android/view/WindowInsetsAnimationCallback.java13
-rw-r--r--core/java/android/view/WindowInsetsAnimationController.java6
-rw-r--r--core/java/android/view/inline/InlineContentView.java5
-rw-r--r--core/java/android/view/inputmethod/InlineSuggestion.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java19
-rw-r--r--core/java/android/view/textclassifier/ConversationAction.java9
-rw-r--r--core/java/android/view/textclassifier/ConversationActions.java13
-rw-r--r--core/java/android/view/textclassifier/EntityConfidence.java7
-rw-r--r--core/java/android/view/textclassifier/GenerateLinksLogger.java7
-rw-r--r--core/java/android/view/textclassifier/ModelFileManager.java21
-rw-r--r--core/java/android/view/textclassifier/SelectionEvent.java10
-rw-r--r--core/java/android/view/textclassifier/SelectionSessionLogger.java13
-rw-r--r--core/java/android/view/textclassifier/SystemTextClassifier.java22
-rw-r--r--core/java/android/view/textclassifier/TextClassification.java4
-rw-r--r--core/java/android/view/textclassifier/TextClassificationContext.java11
-rw-r--r--core/java/android/view/textclassifier/TextClassificationManager.java14
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSession.java9
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSessionId.java5
-rw-r--r--core/java/android/view/textclassifier/TextClassifier.java17
-rw-r--r--core/java/android/view/textclassifier/TextClassifierEvent.java8
-rw-r--r--core/java/android/view/textclassifier/TextClassifierEventTronLogger.java6
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java16
-rw-r--r--core/java/android/view/textclassifier/TextLinks.java11
-rw-r--r--core/java/android/view/textclassifier/TextLinksParams.java7
-rw-r--r--core/java/android/view/textclassifier/TextSelection.java3
-rw-r--r--core/java/android/view/textclassifier/intent/LabeledIntent.java11
-rw-r--r--core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java5
-rw-r--r--core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java10
-rw-r--r--core/java/android/widget/RadioButton.java29
-rw-r--r--core/java/android/widget/RadioGroup.java51
-rw-r--r--core/java/android/widget/Switch.java3
-rw-r--r--core/java/android/widget/ToggleButton.java3
-rw-r--r--core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java7
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java15
-rw-r--r--core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java2
-rw-r--r--core/java/com/android/internal/content/FileSystemProvider.java6
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java4
-rw-r--r--core/java/com/android/internal/infra/ServiceConnector.java6
-rw-r--r--core/java/com/android/internal/infra/WhitelistHelper.java9
-rw-r--r--core/java/com/android/internal/os/FuseAppLoop.java11
-rw-r--r--core/java/com/android/internal/os/Zygote.java26
-rw-r--r--core/java/com/android/internal/os/ZygoteArguments.java8
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java2
-rw-r--r--core/java/com/android/internal/telephony/IPhoneStateListener.aidl1
-rw-r--r--core/java/com/android/internal/telephony/ITelephonyRegistry.aidl13
-rw-r--r--core/java/com/android/internal/util/CollectionUtils.java11
-rw-r--r--core/java/com/android/internal/util/FileRotator.java7
-rw-r--r--core/java/com/android/internal/util/ObjectUtils.java3
-rwxr-xr-xcore/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java3
-rw-r--r--core/java/com/android/internal/view/FloatingActionMode.java12
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl6
-rw-r--r--core/java/com/android/internal/view/menu/StandardMenuPopup.java4
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java26
-rw-r--r--core/java/com/android/internal/widget/LockscreenCredential.java3
-rw-r--r--core/java/com/android/server/SystemConfig.java6
-rw-r--r--core/jni/Android.bp4
-rw-r--r--core/jni/AndroidRuntime.cpp4
-rw-r--r--core/jni/android/graphics/apex/jni_runtime.cpp4
-rw-r--r--core/jni/android_graphics_HardwareRendererObserver.cpp130
-rw-r--r--core/jni/android_graphics_HardwareRendererObserver.h75
-rw-r--r--core/jni/android_hardware_SoundTrigger.cpp1071
-rw-r--r--core/jni/android_media_AudioRecord.cpp2
-rw-r--r--core/jni/android_media_AudioTrack.cpp2
-rw-r--r--core/jni/android_service_DataLoaderService.cpp192
-rw-r--r--core/jni/android_view_FrameMetricsObserver.cpp149
-rw-r--r--core/jni/android_view_FrameMetricsObserver.h71
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp29
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp307
-rw-r--r--core/jni/fd_utils.cpp1
-rw-r--r--core/proto/android/app/settings_enums.proto32
-rw-r--r--core/proto/android/providers/settings/secure.proto9
-rw-r--r--core/proto/android/stats/docsui/docsui_enums.proto1
-rw-r--r--core/proto/android/util/quotatracker.proto219
-rw-r--r--core/res/AndroidManifest.xml15
-rw-r--r--core/res/res/layout/chooser_grid_preview_text.xml3
-rw-r--r--core/res/res/values/config.xml25
-rw-r--r--core/res/res/values/dimens.xml4
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityManagerTest.java122
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java21
-rw-r--r--data/etc/platform.xml1
-rw-r--r--data/etc/privapp-permissions-platform.xml9
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java18
-rw-r--r--graphics/java/android/graphics/HardwareRendererObserver.java103
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java2
-rw-r--r--media/Android.bp73
-rw-r--r--media/java/android/media/MediaMetrics.java634
-rw-r--r--media/java/android/media/MediaRouter2.java210
-rw-r--r--media/java/android/media/MediaRouter2Manager.java86
-rw-r--r--media/java/android/media/MediaScannerConnection.java3
-rw-r--r--media/java/android/media/RingtoneManager.java2
-rw-r--r--media/java/android/media/RouteSessionController.java208
-rw-r--r--media/java/android/media/ThumbnailUtils.java13
-rw-r--r--media/java/android/media/audio/common/AudioChannelMask.aidl217
-rw-r--r--media/java/android/media/audio/common/AudioConfig.aidl36
-rw-r--r--media/java/android/media/audio/common/AudioFormat.aidl170
-rw-r--r--media/java/android/media/audio/common/AudioOffloadInfo.aidl44
-rw-r--r--media/java/android/media/audio/common/AudioStreamType.aidl44
-rw-r--r--media/java/android/media/audio/common/AudioUsage.aidl40
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerDetector.java2
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerManager.java2
-rw-r--r--media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl35
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl47
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl48
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl148
-rw-r--r--media/java/android/media/soundtrigger_middleware/ModelParameter.aidl38
-rw-r--r--media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl28
-rw-r--r--media/java/android/media/soundtrigger_middleware/Phrase.aidl34
-rw-r--r--media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl31
-rw-r--r--media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl35
-rw-r--r--media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl32
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl33
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl51
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl32
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl35
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundModel.aidl36
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundModelType.aidl30
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl31
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl53
-rw-r--r--media/java/android/media/soundtrigger_middleware/Status.aidl29
-rw-r--r--media/java/android/media/tv/TvInputInfo.java2
-rwxr-xr-xmedia/java/android/media/tv/TvInputService.java2
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java16
-rw-r--r--media/java/android/mtp/MtpPropertyList.java3
-rw-r--r--media/java/android/mtp/MtpStorage.java8
-rw-r--r--media/java/android/service/media/MediaBrowserService.java2
-rw-r--r--media/jni/android_media_MediaCodec.cpp8
-rw-r--r--media/jni/android_media_MediaCodec.h4
-rw-r--r--media/jni/android_media_MediaExtractor.cpp2
-rw-r--r--media/jni/android_media_MediaMetricsJNI.cpp67
-rw-r--r--media/jni/android_media_MediaMetricsJNI.h4
-rw-r--r--media/jni/android_media_MediaPlayer.cpp4
-rw-r--r--media/jni/android_media_MediaRecorder.cpp4
-rw-r--r--media/mca/effect/java/android/media/effect/SingleFilterEffect.java3
-rw-r--r--media/mca/filterfw/java/android/filterfw/GraphEnvironment.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Filter.java10
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterContext.java6
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterGraph.java13
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Frame.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FrameFormat.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FrameManager.java5
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java5
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GLFrame.java9
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GraphRunner.java2
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Program.java3
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java7
-rw-r--r--media/mca/filterfw/java/android/filterfw/format/ImageFormat.java2
-rw-r--r--media/mca/filterfw/java/android/filterfw/geometry/Point.java3
-rw-r--r--media/mca/filterfw/java/android/filterfw/geometry/Quad.java4
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java116
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java150
-rw-r--r--opengl/java/android/opengl/EGL14.java4
-rw-r--r--opengl/java/android/opengl/GLES20.java2
-rw-r--r--opengl/java/android/opengl/GLSurfaceView.java2
-rw-r--r--opengl/java/com/google/android/gles_jni/EGLImpl.java3
-rw-r--r--opengl/java/com/google/android/gles_jni/GLImpl.java3
-rw-r--r--opengl/java/javax/microedition/khronos/egl/EGL10.java3
-rw-r--r--packages/CarSystemUI/Android.bp3
-rw-r--r--packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java55
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java16
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java81
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java40
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java2
-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/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java2
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmergencyButton.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/CornerHandleView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java163
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java182
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java214
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt717
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt457
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java158
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java)38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt436
-rw-r--r--rs/java/android/renderscript/BaseObj.java4
-rw-r--r--rs/java/android/renderscript/Element.java2
-rw-r--r--rs/java/android/renderscript/FileA3D.java8
-rw-r--r--rs/java/android/renderscript/Font.java11
-rw-r--r--rs/java/android/renderscript/Matrix4f.java3
-rw-r--r--rs/java/android/renderscript/Mesh.java3
-rw-r--r--rs/java/android/renderscript/Program.java8
-rw-r--r--rs/java/android/renderscript/ProgramFragment.java2
-rw-r--r--rs/java/android/renderscript/ProgramFragmentFixedFunction.java2
-rw-r--r--rs/java/android/renderscript/ProgramRaster.java2
-rw-r--r--rs/java/android/renderscript/ProgramStore.java2
-rw-r--r--rs/java/android/renderscript/ProgramVertex.java2
-rw-r--r--rs/java/android/renderscript/ProgramVertexFixedFunction.java2
-rw-r--r--rs/java/android/renderscript/RSSurfaceView.java2
-rw-r--r--rs/java/android/renderscript/RenderScript.java2
-rw-r--r--rs/java/android/renderscript/RenderScriptCacheDir.java3
-rw-r--r--rs/java/android/renderscript/RenderScriptGL.java2
-rw-r--r--rs/java/android/renderscript/Script.java2
-rw-r--r--services/Android.bp3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java640
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java232
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java371
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java145
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java51
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java430
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java16
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java6
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java248
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java77
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/CountryDetectorService.java76
-rw-r--r--services/core/java/com/android/server/MmsServiceBroker.java22
-rw-r--r--services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java6
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java69
-rw-r--r--services/core/java/com/android/server/SystemService.java23
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java240
-rw-r--r--services/core/java/com/android/server/VibratorService.java63
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java10
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java66
-rw-r--r--services/core/java/com/android/server/attention/AttentionManagerService.java41
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java8
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricServiceBase.java13
-rw-r--r--services/core/java/com/android/server/incremental/IncrementalManagerService.java12
-rw-r--r--services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java4
-rw-r--r--services/core/java/com/android/server/infra/AbstractMasterSystemService.java1
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java28
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java44
-rw-r--r--services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java15
-rw-r--r--services/core/java/com/android/server/integrity/OWNERS1
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java117
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java67
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java150
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java8
-rw-r--r--services/core/java/com/android/server/notification/NotificationComparator.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java8
-rw-r--r--services/core/java/com/android/server/om/OverlayActorEnforcer.java66
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java88
-rw-r--r--services/core/java/com/android/server/om/OverlayReferenceMapper.java375
-rw-r--r--services/core/java/com/android/server/pm/AppsFilter.java57
-rw-r--r--services/core/java/com/android/server/pm/Installer.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java36
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java20
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java55
-rw-r--r--services/core/java/com/android/server/pm/UserSystemPackageInstaller.java75
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java29
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java390
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/HalException.java49
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java77
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java158
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java40
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java53
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java470
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java140
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java709
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java545
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java44
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java117
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java6
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java16
-rw-r--r--services/core/java/com/android/server/utils/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/utils/quota/Categorizer.java35
-rw-r--r--services/core/java/com/android/server/utils/quota/Category.java71
-rw-r--r--services/core/java/com/android/server/utils/quota/QuotaChangeListener.java34
-rw-r--r--services/core/java/com/android/server/utils/quota/Uptc.java88
-rw-r--r--services/core/java/com/android/server/utils/quota/UptcMap.java163
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java150
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotSurface.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java25
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java33
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp17
-rw-r--r--services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp91
-rw-r--r--services/core/jni/onload.cpp4
-rw-r--r--services/java/com/android/server/SystemServer.java26
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java143
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java (renamed from services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java)52
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java218
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java294
-rw-r--r--services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt72
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt206
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java178
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java45
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java47
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java1306
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java65
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java29
-rw-r--r--services/usb/java/com/android/server/usb/UsbSerialReader.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java1
-rw-r--r--telecomm/java/android/telecom/AudioState.java2
-rw-r--r--telecomm/java/android/telecom/Call.java3
-rw-r--r--telecomm/java/android/telecom/CallerInfo.java4
-rw-r--r--telecomm/java/android/telecom/Connection.java11
-rw-r--r--telecomm/java/android/telecom/Log.java2
-rw-r--r--telecomm/java/android/telecom/ParcelableCall.java7
-rw-r--r--telecomm/java/android/telecom/Phone.java2
-rw-r--r--telecomm/java/android/telecom/PhoneAccountHandle.java2
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java99
-rw-r--r--telecomm/java/android/telecom/VideoCallImpl.java2
-rw-r--r--telecomm/java/android/telecom/VideoProfile.java1
-rw-r--r--telephony/common/com/android/internal/telephony/GsmAlphabet.java2
-rw-r--r--telephony/common/com/android/internal/telephony/SmsApplication.java3
-rw-r--r--telephony/common/com/android/internal/telephony/SmsConstants.java2
-rw-r--r--telephony/common/com/google/android/mms/ContentType.java2
-rw-r--r--telephony/common/com/google/android/mms/InvalidHeaderValueException.java2
-rw-r--r--telephony/common/com/google/android/mms/MmsException.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/Base64.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/CharacterSets.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/DeliveryInd.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/EncodedStringValue.java3
-rw-r--r--telephony/common/com/google/android/mms/pdu/GenericPdu.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/NotificationInd.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/NotifyRespInd.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/PduBody.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/PduComposer.java3
-rw-r--r--telephony/common/com/google/android/mms/pdu/PduContentTypes.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/PduHeaders.java2
-rwxr-xr-xtelephony/common/com/google/android/mms/pdu/PduParser.java3
-rw-r--r--telephony/common/com/google/android/mms/pdu/PduPart.java3
-rwxr-xr-xtelephony/common/com/google/android/mms/pdu/PduPersister.java3
-rw-r--r--telephony/common/com/google/android/mms/pdu/QuotedPrintable.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/ReadOrigInd.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/ReadRecInd.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/RetrieveConf.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/SendConf.java2
-rw-r--r--telephony/common/com/google/android/mms/pdu/SendReq.java3
-rw-r--r--telephony/common/com/google/android/mms/util/AbstractCache.java3
-rw-r--r--telephony/common/com/google/android/mms/util/DownloadDrmHelper.java3
-rw-r--r--telephony/common/com/google/android/mms/util/DrmConvertSession.java3
-rw-r--r--telephony/common/com/google/android/mms/util/PduCache.java3
-rw-r--r--telephony/common/com/google/android/mms/util/PduCacheEntry.java2
-rw-r--r--telephony/common/com/google/android/mms/util/SqliteWrapper.java3
-rw-r--r--telephony/java/android/service/euicc/EuiccProfileInfo.java2
-rw-r--r--telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java2
-rw-r--r--telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java2
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java56
-rw-r--r--telephony/java/android/telephony/CbGeoUtils.java76
-rw-r--r--telephony/java/android/telephony/CellIdentityGsm.java2
-rw-r--r--telephony/java/android/telephony/CellIdentityLte.java2
-rw-r--r--telephony/java/android/telephony/ModemActivityInfo.java4
-rw-r--r--telephony/java/android/telephony/PreciseDataConnectionState.java179
-rw-r--r--telephony/java/android/telephony/SmsManager.java21
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java51
-rw-r--r--telephony/java/com/android/internal/telephony/ISms.aidl8
-rw-r--r--telephony/java/com/android/internal/telephony/ISmsImplBase.java5
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl5
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneConstantConversions.java8
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneConstants.java22
-rw-r--r--telephony/java/com/android/internal/telephony/TelephonyIntents.java16
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java521
-rw-r--r--test-mock/Android.bp2
-rw-r--r--test-mock/src/android/test/mock/MockContext.java10
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java24
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java32
-rw-r--r--tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java10
-rw-r--r--tools/aapt2/ResourceUtils.cpp8
-rw-r--r--tools/stats_log_api_gen/Android.bp14
-rw-r--r--tools/stats_log_api_gen/Collation.cpp7
-rw-r--r--tools/stats_log_api_gen/Collation.h3
-rw-r--r--tools/stats_log_api_gen/atoms_info_writer.cpp227
-rw-r--r--tools/stats_log_api_gen/atoms_info_writer.h36
-rw-r--r--tools/stats_log_api_gen/main.cpp324
-rw-r--r--tools/stats_log_api_gen/utils.cpp104
-rw-r--r--tools/stats_log_api_gen/utils.h11
-rw-r--r--wifi/Android.bp17
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pConfig.java35
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pGroup.java7
554 files changed, 19940 insertions, 7427 deletions
diff --git a/Android.bp b/Android.bp
index 9ffdd1d6c8fe..787b48b58cd6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -203,7 +203,6 @@ filegroup {
name: "framework-non-updatable-sources",
srcs: [
// Java/AIDL sources under frameworks/base
- ":framework-appsearch-sources",
":framework-blobstore-sources",
":framework-core-sources",
":framework-drm-sources",
@@ -246,7 +245,7 @@ filegroup {
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
// TODO: this needs to be removed when statsd-framework.jar is separated out
- ":statsd_aidl",
+ ":statsd_java_aidl",
":storaged_aidl",
":vold_aidl",
@@ -262,6 +261,7 @@ filegroup {
filegroup {
name: "framework-updatable-sources",
srcs: [
+ ":framework-appsearch-sources",
":framework-sdkext-sources",
":framework-statsd-sources",
":updatable-media-srcs",
@@ -427,6 +427,7 @@ java_library {
defaults: ["framework-defaults"],
srcs: [":framework-non-updatable-sources"],
libs: [
+ "framework-appsearch-stubs",
// TODO(b/146167933): Use framework-statsd-stubs
"framework-statsd",
"framework-wifi-stubs",
@@ -466,6 +467,7 @@ java_library {
installable: false, // this lib is a build-only library
static_libs: [
"framework-minus-apex",
+ "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
"framework-sdkext-stubs-systemapi",
// TODO(b/146167933): Use framework-statsd-stubs instead.
"framework-statsd",
@@ -797,11 +799,7 @@ cc_library {
filegroup {
name: "incremental_aidl",
srcs: [
- "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
- "core/java/android/os/incremental/IIncrementalManager.aidl",
- "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
- "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
],
path: "core/java",
}
@@ -809,7 +807,20 @@ filegroup {
filegroup {
name: "dataloader_aidl",
srcs: [
+ "core/java/android/content/pm/DataLoaderParamsParcel.aidl",
+ "core/java/android/content/pm/FileSystemControlParcel.aidl",
"core/java/android/content/pm/IDataLoaderStatusListener.aidl",
+ "core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl",
+ "core/java/android/content/pm/NamedParcelFileDescriptor.aidl",
+ ],
+ path: "core/java",
+}
+
+filegroup {
+ name: "incremental_manager_aidl",
+ srcs: [
+ "core/java/android/os/incremental/IIncrementalManager.aidl",
+ "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
],
path: "core/java",
}
@@ -819,9 +830,6 @@ aidl_interface {
srcs: [
":incremental_aidl",
],
- imports: [
- "libdataloader_aidl",
- ],
backend: {
java: {
sdk_version: "28",
@@ -840,6 +848,9 @@ aidl_interface {
srcs: [
":dataloader_aidl",
],
+ imports: [
+ "libincremental_aidl",
+ ],
backend: {
java: {
sdk_version: "28",
@@ -848,8 +859,30 @@ aidl_interface {
enabled: true,
},
ndk: {
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "libincremental_manager_aidl",
+ srcs: [
+ ":incremental_manager_aidl",
+ ],
+ imports: [
+ "libincremental_aidl",
+ "libdataloader_aidl",
+ ],
+ backend: {
+ java: {
+ sdk_version: "28",
+ },
+ cpp: {
enabled: true,
},
+ ndk: {
+ enabled: false,
+ },
},
}
@@ -1658,6 +1691,8 @@ filegroup {
"core/java/android/util/LocalLog.java",
"core/java/android/util/TimeUtils.java",
"core/java/com/android/internal/os/SomeArgs.java",
+ "core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IState.java",
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f94de29d41b1..b84e71526a14 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -257,6 +257,8 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/ext.jar)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/google/android/mms)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/*-service.jar)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/apex/Android.bp b/apex/Android.bp
index 85d72c76fb48..56f7db2c8dad 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -26,6 +26,9 @@ mainline_stubs_args =
"--hide Typo " +
"--hide UnavailableSymbol "
+// TODO: remove this server classes are cleaned up.
+mainline_stubs_args += "--hide-package com.android.server "
+
stubs_defaults {
name: "framework-module-stubs-defaults-publicapi",
args: mainline_stubs_args,
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index bcdcc7da8070..b014fdcb3df3 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -14,9 +14,11 @@
apex {
name: "com.android.appsearch",
-
manifest: "apex_manifest.json",
-
+ java_libs: [
+ "framework-appsearch",
+ "service-appsearch",
+ ],
key: "com.android.appsearch.key",
certificate: ":com.android.appsearch.certificate",
}
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 0a65f7320d8a..3dc5a2c10b6b 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -23,17 +23,52 @@ filegroup {
java_library {
name: "framework-appsearch",
- installable: false,
- sdk_version: "core_platform",
- srcs: [
- ":framework-appsearch-sources",
- ],
- aidl: {
- export_include_dirs: [
- "java",
- ],
- },
+ installable: true,
+ sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+ srcs: [":framework-appsearch-sources"],
libs: [
- "framework-minus-apex",
+ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
],
}
+
+metalava_appsearch_docs_args =
+ "--hide-package com.android.server " +
+ "--error UnhiddenSystemApi " +
+ "--hide RequiresPermission " +
+ "--hide MissingPermission " +
+ "--hide BroadcastBehavior " +
+ "--hide HiddenSuperclass " +
+ "--hide DeprecationMismatch " +
+ "--hide UnavailableSymbol " +
+ "--hide SdkConstant " +
+ "--hide HiddenTypeParameter " +
+ "--hide Todo --hide Typo " +
+ "--hide HiddenTypedefConstant " +
+ "--show-annotation android.annotation.SystemApi "
+
+droidstubs {
+ name: "framework-appsearch-stubs-srcs",
+ srcs: [
+ ":framework-annotations",
+ ":framework-appsearch-sources",
+ ],
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ },
+ args: metalava_appsearch_docs_args,
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+}
+
+java_library {
+ name: "framework-appsearch-stubs",
+ srcs: [":framework-appsearch-stubs-srcs"],
+ aidl: {
+ export_include_dirs: [
+ "java",
+ ],
+ },
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+ installable: false,
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index fcebe3d9cf3c..02cc967e7daf 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -15,19 +15,25 @@
*/
package android.app.appsearch;
+import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.content.Context;
/**
- * This is where the AppSearchManagerService wrapper is registered.
+ * Class holding initialization code for the AppSearch module.
*
- * TODO(b/142567528): add comments when implement this class
* @hide
*/
+@SystemApi
public class AppSearchManagerFrameworkInitializer {
+ private AppSearchManagerFrameworkInitializer() {}
/**
- * TODO(b/142567528): add comments when implement this class
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all AppSearch
+ * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides
+ * {@link SystemServiceRegistry}
*/
public static void initialize() {
SystemServiceRegistry.registerStaticService(
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index e6ca544c04be..aed6ad9fc08a 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -17,6 +17,18 @@
// TODO(b/145815909): move StatsDimensionsValue.aidl and StatsLogEventWrapper.aidl here
filegroup {
name: "statsd_aidl",
+ srcs: [
+ "android/os/IPullAtomCallback.aidl",
+ "android/os/IPullAtomResultReceiver.aidl",
+ "android/os/IStatsCompanionService.aidl",
+ "android/os/IStatsd.aidl",
+ "android/os/IStatsPullerCallback.aidl",
+ "android/util/StatsEventParcel.aidl",
+ ],
+}
+
+filegroup {
+ name: "statsd_java_aidl",
srcs: ["**/*.aidl"],
}
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
new file mode 100644
index 000000000000..45ba3a21ed5b
--- /dev/null
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -0,0 +1,65 @@
+/**
+ * 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 android.os;
+
+import android.app.PendingIntent;
+
+/**
+ * Binder interface to communicate with the Java-based statistics service helper.
+ * Contains parcelable objects available only in Java.
+ * {@hide}
+ */
+interface IStatsManagerService {
+
+ /**
+ * Registers the given pending intent for this config key. This intent is invoked when the
+ * memory consumed by the metrics for this configuration approach the pre-defined limits. There
+ * can be at most one listener per config key.
+ *
+ * Requires Manifest.permission.DUMP.
+ */
+ void setDataFetchOperation(long configKey, in PendingIntent pendingIntent,
+ in String packageName);
+
+ /**
+ * Registers the given pending intent for this packagename. This intent is invoked when the
+ * active status of any of the configs sent by this package changes and will contain a list of
+ * config ids that are currently active. It also returns the list of configs that are currently
+ * active. There can be at most one active configs changed listener per package.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ long[] setActiveConfigsChangedOperation(in PendingIntent pendingIntent, in String packageName);
+
+ /**
+ * Set the PendingIntent to be used when broadcasting subscriber
+ * information to the given subscriberId within the given config.
+ *
+ * Suppose that the calling uid has added a config with key configKey, and that in this config
+ * it is specified that when a particular anomaly is detected, a broadcast should be sent to
+ * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
+ * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
+ * when the anomaly is detected.
+ *
+ * This function can only be called by the owner (uid) of the config. It must be called each
+ * time statsd starts. Later calls overwrite previous calls; only one PendingIntent is stored.
+ *
+ * Requires Manifest.permission.DUMP.
+ */
+ void setBroadcastSubscriber(long configKey, long subscriberId, in PendingIntent pendingIntent,
+ in String packageName);
+} \ No newline at end of file
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
new file mode 100644
index 000000000000..71b52e26b7db
--- /dev/null
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -0,0 +1,70 @@
+/*
+ * 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.server.stats;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * @hide
+ */
+public class StatsCompanion {
+ private static final String TAG = "StatsCompanion";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Lifecycle class for both {@link StatsCompanionService} and {@link StatsManagerService}.
+ */
+ public static final class Lifecycle extends SystemService {
+ private StatsCompanionService mStatsCompanionService;
+ private StatsManagerService mStatsManagerService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mStatsCompanionService = new StatsCompanionService(getContext());
+ mStatsManagerService = new StatsManagerService(getContext());
+ mStatsCompanionService.setStatsManagerService(mStatsManagerService);
+ mStatsManagerService.setStatsCompanionService(mStatsCompanionService);
+
+ try {
+ publishBinderService(Context.STATS_COMPANION_SERVICE,
+ mStatsCompanionService);
+ if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
+ publishBinderService(Context.STATS_MANAGER_SERVICE,
+ mStatsManagerService);
+ if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_MANAGER_SERVICE);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to publishBinderService", e);
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mStatsCompanionService.systemReady();
+ mStatsManagerService.systemReady();
+ }
+ }
+ }
+}
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index bc7716e5f6eb..157da636397e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -137,7 +137,6 @@ import com.android.internal.os.StoragedUidIoStatsReader;
import com.android.internal.util.DumpUtils;
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalServices;
-import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.MemoryStat;
import com.android.server.notification.NotificationManagerService;
@@ -278,6 +277,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
private final BroadcastReceiver mUserUpdateReceiver;
private final ShutdownEventReceiver mShutdownEventReceiver;
+ private StatsManagerService mStatsManagerService;
+
private static final class PullerKey {
private final int mUid;
private final int mAtomTag;
@@ -2681,6 +2682,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
Slog.d(TAG, "learned that statsdReady");
}
sayHiToStatsd(); // tell statsd that we're ready too and link to it
+ mStatsManagerService.systemReady();
mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED)
.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
UserHandle.SYSTEM, android.Manifest.permission.DUMP);
@@ -2736,7 +2738,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
- // Lifecycle and related code
+ // Statsd related code
/**
* Fetches the statsd IBinder service.
@@ -2747,42 +2749,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
return IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
}
- public static final class Lifecycle extends SystemService {
- private StatsCompanionService mStatsCompanionService;
-
- public Lifecycle(Context context) {
- super(context);
- }
-
- @Override
- public void onStart() {
- mStatsCompanionService = new StatsCompanionService(getContext());
- try {
- publishBinderService(Context.STATS_COMPANION_SERVICE,
- mStatsCompanionService);
- if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
- } catch (Exception e) {
- Slog.e(TAG, "Failed to publishBinderService", e);
- }
- }
-
- @Override
- public void onBootPhase(int phase) {
- super.onBootPhase(phase);
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mStatsCompanionService.systemReady();
- }
- }
- }
-
/**
* Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
*/
- private void systemReady() {
+ void systemReady() {
if (DEBUG) Slog.d(TAG, "Learned that systemReady");
sayHiToStatsd();
}
+ void setStatsManagerService(StatsManagerService statsManagerService) {
+ mStatsManagerService = statsManagerService;
+ }
+
/**
* Tells statsd that statscompanion is ready. If the binder call returns, link to
* statsd.
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
new file mode 100644
index 000000000000..f3bf9099c893
--- /dev/null
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -0,0 +1,108 @@
+/*
+ * 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.server.stats;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IStatsManagerService;
+import android.os.IStatsd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * @hide
+ */
+public class StatsManagerService extends IStatsManagerService.Stub {
+
+ private static final String TAG = "StatsManagerService";
+ private static final boolean DEBUG = false;
+
+ @GuardedBy("sStatsdLock")
+ private static IStatsd sStatsd;
+ private static final Object sStatsdLock = new Object();
+
+ private StatsCompanionService mStatsCompanionService;
+
+ public StatsManagerService(Context context) {
+ super();
+ }
+
+ @Override
+ public void setDataFetchOperation(long configKey, PendingIntent pendingIntent,
+ String packageName) {
+ // no-op
+ if (DEBUG) {
+ Slog.d(TAG, "setDataFetchOperation");
+ }
+ }
+
+ @Override
+ public long[] setActiveConfigsChangedOperation(PendingIntent pendingIntent,
+ String packageName) {
+ // no-op
+ if (DEBUG) {
+ Slog.d(TAG, "setActiveConfigsChangedOperation");
+ }
+ return new long[]{};
+ }
+
+ @Override
+ public void setBroadcastSubscriber(long configKey, long subscriberId,
+ PendingIntent pendingIntent, String packageName) {
+ //no-op
+ if (DEBUG) {
+ Slog.d(TAG, "setBroadcastSubscriber");
+ }
+ }
+
+ void setStatsCompanionService(StatsCompanionService statsCompanionService) {
+ mStatsCompanionService = statsCompanionService;
+ }
+
+ void systemReady() {
+ if (DEBUG) {
+ Slog.d(TAG, "statsdReady");
+ }
+ setupStatsManagerService();
+ }
+
+ private void setupStatsManagerService() {
+ synchronized (sStatsdLock) {
+ if (sStatsd != null) {
+ if (DEBUG) {
+ Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
+ new IllegalStateException(
+ "sStatsd is not null when being fetched"));
+ }
+ return;
+ }
+ sStatsd = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
+ // Assume statsd is ready since this is called form statscompanion, link to statsd.
+ try {
+ sStatsd.asBinder().linkToDeath((IBinder.DeathRecipient) () -> {
+ sStatsd = null;
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
+ }
+ }
+ }
+}
diff --git a/api/current.txt b/api/current.txt
index 7e7cd9dfb66e..36374bbbc45b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22653,6 +22653,7 @@ package android.inputmethodservice {
method public void onConfigureWindow(android.view.Window, boolean, boolean);
method public android.view.View onCreateCandidatesView();
method public android.view.View onCreateExtractTextView();
+ method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest();
method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
method public android.view.View onCreateInputView();
@@ -22669,6 +22670,7 @@ package android.inputmethodservice {
method public void onFinishInput();
method public void onFinishInputView(boolean);
method public void onInitializeInterface();
+ method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse);
method public boolean onKeyDown(int, android.view.KeyEvent);
method public boolean onKeyLongPress(int, android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -30646,6 +30648,10 @@ package android.net.wifi.p2p {
ctor public WifiP2pConfig();
ctor public WifiP2pConfig(android.net.wifi.p2p.WifiP2pConfig);
method public int describeContents();
+ method public int getGroupOwnerBand();
+ method public int getNetworkId();
+ method @Nullable public String getNetworkName();
+ method @Nullable public String getPassphrase();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pConfig> CREATOR;
field public static final int GROUP_OWNER_BAND_2GHZ = 1; // 0x1
@@ -30719,6 +30725,8 @@ package android.net.wifi.p2p {
method public boolean isGroupOwner();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pGroup> CREATOR;
+ field public static final int PERSISTENT_NET_ID = -2; // 0xfffffffe
+ field public static final int TEMPORARY_NET_ID = -1; // 0xffffffff
}
public class WifiP2pInfo implements android.os.Parcelable {
@@ -35029,6 +35037,7 @@ package android.os {
method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
+ method public static int getSuggestedMaxIpcSizeBytes();
method public boolean isBinderAlive();
method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
method public boolean pingBinder();
@@ -35225,6 +35234,7 @@ package android.os {
method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+ method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -35272,6 +35282,7 @@ package android.os {
method public void writeNoException();
method public void writeParcelable(@Nullable android.os.Parcelable, int);
method public <T extends android.os.Parcelable> void writeParcelableArray(@Nullable T[], int);
+ method public void writeParcelableCreator(@NonNull android.os.Parcelable);
method public <T extends android.os.Parcelable> void writeParcelableList(@Nullable java.util.List<T>, int);
method public void writePersistableBundle(@Nullable android.os.PersistableBundle);
method public void writeSerializable(@Nullable java.io.Serializable);
@@ -36022,7 +36033,8 @@ package android.os.storage {
method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException;
method public String getMountedObbPath(String);
method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume();
- method @Nullable public android.os.storage.StorageVolume getStorageVolume(java.io.File);
+ method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes();
+ method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
@@ -36048,6 +36060,8 @@ package android.os.storage {
method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
method public int describeContents();
method public String getDescription(android.content.Context);
+ method @Nullable public java.io.File getDirectory();
+ method @Nullable public String getMediaStoreVolumeName();
method public String getState();
method @Nullable public String getUuid();
method public boolean isEmulated();
@@ -38710,19 +38724,21 @@ package android.provider {
public final class MediaStore {
ctor public MediaStore();
+ method @NonNull public static android.app.PendingIntent createDeleteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
+ method @NonNull public static android.app.PendingIntent createFavoriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+ method @NonNull public static android.app.PendingIntent createTrashRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+ method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
method public static android.net.Uri getMediaScannerUri();
method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
method public static boolean getRequireOriginal(@NonNull android.net.Uri);
method @NonNull public static String getVersion(@NonNull android.content.Context);
method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
- method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
- method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
- method public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
field public static final String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -38982,6 +38998,7 @@ package android.provider {
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
@@ -39064,6 +39081,7 @@ package android.provider {
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
field @Deprecated public static final String DATA = "_data";
@@ -39127,6 +39145,7 @@ package android.provider {
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
+ field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
field public static final String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
@@ -39138,7 +39157,7 @@ package android.provider {
field public static final String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
- field public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
+ field @Deprecated public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
field public static final String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
field public static final String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -39201,6 +39220,7 @@ package android.provider {
field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final String EXTRA_AUTHORITIES = "authorities";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED = "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED";
field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
@@ -41570,7 +41590,8 @@ package android.service.autofill {
method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
method public int getFlags();
method public int getId();
- method public void writeToParcel(android.os.Parcel, int);
+ method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
@@ -41587,6 +41608,7 @@ package android.service.autofill {
public static final class FillResponse.Builder {
ctor public FillResponse.Builder();
method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
+ method @NonNull public android.service.autofill.FillResponse.Builder addInlineSuggestionSlice(@NonNull android.app.slice.Slice);
method @NonNull public android.service.autofill.FillResponse build();
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
@@ -43892,6 +43914,7 @@ package android.telecom {
field public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final String EVENT_CALL_REMOTELY_HELD = "android.telecom.event.CALL_REMOTELY_HELD";
field public static final String EVENT_CALL_REMOTELY_UNHELD = "android.telecom.event.CALL_REMOTELY_UNHELD";
+ field public static final String EVENT_CALL_SWITCH_FAILED = "android.telecom.event.CALL_SWITCH_FAILED";
field public static final String EVENT_MERGE_COMPLETE = "android.telecom.event.MERGE_COMPLETE";
field public static final String EVENT_MERGE_START = "android.telecom.event.MERGE_START";
field public static final String EVENT_ON_HOLD_TONE_END = "android.telecom.event.ON_HOLD_TONE_END";
@@ -44349,19 +44372,27 @@ package android.telecom {
field @Deprecated public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
field public static final String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
+ field public static final String ACTION_POST_CALL = "android.telecom.action.POST_CALL";
field public static final String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
field public static final String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
+ field public static final int DURATION_LONG = 3; // 0x3
+ field public static final int DURATION_MEDIUM = 2; // 0x2
+ field public static final int DURATION_SHORT = 1; // 0x1
+ field public static final int DURATION_VERY_SHORT = 0; // 0x0
field public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
+ field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telecom.extra.CALL_NETWORK_TYPE";
field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
field public static final String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
field public static final String EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME = "android.telecom.extra.DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME";
+ field public static final String EXTRA_DISCONNECT_CAUSE = "android.telecom.extra.DISCONNECT_CAUSE";
+ field public static final String EXTRA_HANDLE = "android.telecom.extra.HANDLE";
field public static final String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
field public static final String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
field public static final String EXTRA_INCOMING_VIDEO_STATE = "android.telecom.extra.INCOMING_VIDEO_STATE";
@@ -44601,6 +44632,7 @@ package android.telephony {
method public void notifyConfigChangedForSubId(int);
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+ field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -44611,6 +44643,7 @@ package android.telephony {
field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
field public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = "allow_emergency_video_calls_bool";
+ field public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL = "allow_holding_video_call";
field public static final String KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL = "allow_hold_call_during_emergency_bool";
field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
@@ -44660,11 +44693,16 @@ package android.telephony {
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
- field public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING = "config_ims_mmtel_package_override_string";
+ field @Deprecated public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+ field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+ field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+ field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
@@ -45224,6 +45262,7 @@ package android.telephony {
method public void onDataConnectionStateChanged(int);
method public void onDataConnectionStateChanged(int, int);
method public void onMessageWaitingIndicatorChanged(boolean);
+ method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onServiceStateChanged(android.telephony.ServiceState);
method @Deprecated public void onSignalStrengthChanged(int);
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
@@ -45238,12 +45277,23 @@ package android.telephony {
field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
field public static final int LISTEN_NONE = 0; // 0x0
+ field @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getLastCauseCode();
+ method @Nullable public android.net.LinkProperties getLinkProperties();
+ method public int getNetworkType();
+ method public int getState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ }
+
public final class RadioAccessSpecifier implements android.os.Parcelable {
ctor public RadioAccessSpecifier(int, int[], int[]);
method public int describeContents();
@@ -45756,6 +45806,7 @@ package android.telephony {
field public static final int DATA_CONNECTED = 2; // 0x2
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
+ field public static final int DATA_DISCONNECTING = 4; // 0x4
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final int DATA_UNKNOWN = -1; // 0xffffffff
field public static final String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
@@ -52776,6 +52827,7 @@ package android.view {
public static final class WindowInsetsAnimationCallback.InsetsAnimation {
ctor public WindowInsetsAnimationCallback.InsetsAnimation(int, @Nullable android.view.animation.Interpolator, long);
+ method @FloatRange(from=0.0f, to=1.0f) public float getAlpha();
method public long getDurationMillis();
method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
method public float getInterpolatedFraction();
@@ -52792,6 +52844,7 @@ package android.view {
public interface WindowInsetsAnimationController {
method public void finish(boolean);
+ method public float getCurrentAlpha();
method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
method @NonNull public android.graphics.Insets getCurrentInsets();
method @NonNull public android.graphics.Insets getHiddenStateInsets();
diff --git a/api/removed.txt b/api/removed.txt
index 1a2f43450d7b..8b30d0a5cf39 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -436,11 +436,12 @@ package android.provider {
}
public final class MediaStore {
- method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams);
method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
- method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
+ method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
+ method @Deprecated public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
}
public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
@@ -470,22 +471,6 @@ package android.provider {
field @Deprecated public static final String GROUP_ID = "group_id";
}
- @Deprecated public static class MediaStore.PendingParams {
- ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String);
- method public void setDownloadUri(@Nullable android.net.Uri);
- method public void setRefererUri(@Nullable android.net.Uri);
- method public void setRelativePath(@Nullable String);
- }
-
- @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable {
- method public void abandon();
- method public void close();
- method public void notifyProgress(@IntRange(from=0, to=100) int);
- method @NonNull public android.os.ParcelFileDescriptor open() throws java.io.FileNotFoundException;
- method @NonNull public java.io.OutputStream openOutputStream() throws java.io.FileNotFoundException;
- method @NonNull public android.net.Uri publish();
- }
-
public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ARTIST = "artist";
diff --git a/api/system-current.txt b/api/system-current.txt
index 75045823a1f2..b2bf2c603eb7 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -811,6 +811,14 @@ package android.app.admin {
}
+package android.app.appsearch {
+
+ public class AppSearchManagerFrameworkInitializer {
+ method public static void initialize();
+ }
+
+}
+
package android.app.assist {
public static class AssistStructure.ViewNode {
@@ -6357,6 +6365,7 @@ package android.os {
}
public class Environment {
+ method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
method @NonNull public static java.io.File getOdmDirectory();
method @NonNull public static java.io.File getOemDirectory();
method @NonNull public static java.io.File getProductDirectory();
@@ -7245,8 +7254,8 @@ package android.provider {
}
public final class MediaStore {
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
}
public abstract class SearchIndexableData {
@@ -8039,6 +8048,7 @@ package android.service.notification {
field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
field public static final String KEY_IMPORTANCE = "key_importance";
field public static final String KEY_PEOPLE = "key_people";
+ field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -8712,6 +8722,13 @@ package android.telephony {
public class CbGeoUtils {
}
+ public static class CbGeoUtils.Circle implements android.telephony.CbGeoUtils.Geometry {
+ ctor public CbGeoUtils.Circle(@NonNull android.telephony.CbGeoUtils.LatLng, double);
+ method public boolean contains(@NonNull android.telephony.CbGeoUtils.LatLng);
+ method @NonNull public android.telephony.CbGeoUtils.LatLng getCenter();
+ method public double getRadius();
+ }
+
public static interface CbGeoUtils.Geometry {
method public boolean contains(@NonNull android.telephony.CbGeoUtils.LatLng);
}
@@ -8724,6 +8741,12 @@ package android.telephony {
field public final double lng;
}
+ public static class CbGeoUtils.Polygon implements android.telephony.CbGeoUtils.Geometry {
+ ctor public CbGeoUtils.Polygon(@NonNull java.util.List<android.telephony.CbGeoUtils.LatLng>);
+ method public boolean contains(@NonNull android.telephony.CbGeoUtils.LatLng);
+ method @NonNull public java.util.List<android.telephony.CbGeoUtils.LatLng> getVertices();
+ }
+
public class CellBroadcastIntents {
method public static void sendOrderedBroadcastForBackgroundReceivers(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
}
@@ -9207,6 +9230,7 @@ package android.telephony {
method public int getSleepTimeMillis();
method public long getTimestamp();
method @NonNull public java.util.List<android.telephony.ModemActivityInfo.TransmitPower> getTransmitPowerInfo();
+ method public boolean isValid();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
field public static final int TX_POWER_LEVELS = 5; // 0x5
@@ -9320,7 +9344,6 @@ package android.telephony {
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
- method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onRadioPowerStateChanged(int);
method public void onSrvccStateChanged(int);
method public void onVoiceActivationStateChanged(int);
@@ -9330,7 +9353,6 @@ package android.telephony {
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
- field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
@@ -9356,13 +9378,12 @@ package android.telephony {
}
public final class PreciseDataConnectionState implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public String getDataConnectionApn();
- method public int getDataConnectionApnTypeBitMask();
- method public int getDataConnectionFailCause();
- method public int getDataConnectionState();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ method @Deprecated @NonNull public String getDataConnectionApn();
+ method @Deprecated public int getDataConnectionApnTypeBitMask();
+ method @Deprecated public int getDataConnectionFailCause();
+ method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
+ method @Deprecated public int getDataConnectionNetworkType();
+ method @Deprecated public int getDataConnectionState();
}
public final class PreciseDisconnectCause {
@@ -9601,6 +9622,7 @@ package android.telephony {
public final class SmsManager {
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
}
@@ -9779,6 +9801,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
+ field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
diff --git a/api/test-current.txt b/api/test-current.txt
index 3bdd4790372b..219258ef50b0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -697,6 +697,7 @@ package android.content {
public abstract class Context {
method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public java.io.File getCrateDir(@NonNull String);
method public abstract android.view.Display getDisplay();
method public abstract int getDisplayId();
method public android.os.UserHandle getUser();
@@ -2467,14 +2468,9 @@ package android.provider {
}
public final class MediaStore {
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
- method public static android.net.Uri scanFile(android.content.Context, java.io.File);
- method public static android.net.Uri scanFileFromShell(android.content.Context, java.io.File);
- method public static void scanVolume(android.content.Context, java.io.File);
- method public static void waitForIdle(android.content.Context);
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+ method public static void waitForIdle(@NonNull android.content.ContentResolver);
}
public final class Settings {
@@ -2832,6 +2828,7 @@ package android.service.notification {
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
field public static final String KEY_IMPORTANCE = "key_importance";
+ field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -3162,6 +3159,10 @@ package android.telephony {
field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int);
+ }
+
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
method public void setCdmaSystemAndNetworkId(int, int);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 484f82330055..afff61497157 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -146,6 +146,7 @@ cc_defaults {
"libprotoutil",
"libservices",
"libstatslog",
+ "libstatsmetadata",
"libstatssocket",
"libsysutils",
"libtimestats_proto",
@@ -153,6 +154,63 @@ cc_defaults {
],
}
+// ================
+// libstatsmetadata
+// ================
+
+genrule {
+ name: "atoms_info.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h",
+ out: [
+ "atoms_info.h",
+ ],
+}
+
+genrule {
+ name: "atoms_info.cpp",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp",
+ out: [
+ "atoms_info.cpp",
+ ],
+}
+
+cc_library_shared {
+ name: "libstatsmetadata",
+ host_supported: true,
+ generated_sources: [
+ "atoms_info.cpp",
+ ],
+ generated_headers: [
+ "atoms_info.h",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ export_generated_headers: [
+ "atoms_info.h",
+ ],
+ shared_libs: [
+ "libcutils",
+ "libstatslog",
+ ],
+ target: {
+ android: {
+ shared_libs: [
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libutils",
+ ],
+ },
+ },
+}
+
+
// =========
// statsd
// =========
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 84a06070e431..a545fc5718d0 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -18,8 +18,8 @@
#include "Log.h"
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "atoms_info.h"
#include "math.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 98d41c22d540..34818145a922 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -27,6 +27,7 @@
#include <utils/SystemClock.h>
#include "android-base/stringprintf.h"
+#include "atoms_info.h"
#include "external/StatsPullerManager.h"
#include "guardrail/StatsdStats.h"
#include "metrics/CountMetricProducer.h"
diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/cmds/statsd/src/external/PowerStatsPuller.cpp
index b142caca3acc..dc69b78f0329 100644
--- a/cmds/statsd/src/external/PowerStatsPuller.cpp
+++ b/cmds/statsd/src/external/PowerStatsPuller.cpp
@@ -22,6 +22,7 @@
#include <vector>
#include "PowerStatsPuller.h"
+#include "statslog.h"
#include "stats_log_util.h"
using android::hardware::hidl_vec;
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index 53fa6301a836..031c43740d9d 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -18,8 +18,8 @@
#include "Log.h"
#include "StatsPullerManager.h"
+#include "atoms_info.h"
#include "puller_util.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 23d2aceb4fc3..564b9ee8051c 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -16,7 +16,7 @@
#pragma once
#include "config/ConfigKey.h"
-#include "statslog.h"
+#include "atoms_info.h"
#include <gtest/gtest_prod.h>
#include <log/log_time.h>
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 464cec36d5e3..088f607ecfce 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -23,6 +23,7 @@
#include <utils/SystemClock.h>
#include "CountMetricProducer.h"
+#include "atoms_info.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "guardrail/StatsdStats.h"
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 9131802c83e7..2ad8217c45d4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -22,6 +22,7 @@
#include <inttypes.h>
+#include "atoms_info.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "condition/StateConditionTracker.h"
@@ -36,7 +37,6 @@
#include "metrics/ValueMetricProducer.h"
#include "state/StateManager.h"
#include "stats_util.h"
-#include "statslog.h"
using std::set;
using std::string;
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index e65325a52b98..7453370f25fd 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -15,7 +15,7 @@
*/
#pragma once
-#include <statslog.h>
+#include <atoms_info.h>
#include <utils/RefBase.h>
#include "HashableDimensionKey.h"
#include "logd/LogEvent.h"
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 0a86363a7090..f3e94331a23e 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -19,9 +19,9 @@
#include <android/util/ProtoOutputStream.h>
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "atoms_info.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "guardrail/StatsdStats.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
index e91fb0d4b27c..6abdfa3dbb24 100644
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
@@ -24,6 +24,7 @@
#include <log/log.h>
#include "src/external/GpuStatsPuller.h"
+#include "statslog.h"
#ifdef __ANDROID__
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
index 5c9636fa99cc..5b7a30d4a5aa 100644
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "SurfaceflingerStatsPuller_test"
#include "src/external/SurfaceflingerStatsPuller.h"
+#include "statslog.h"
#include <gtest/gtest.h>
#include <log/log.h>
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 266ea351b5d4..673082834e18 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include <stdio.h>
#include <vector>
+#include "statslog.h"
#include "../metrics/metrics_test_helper.h"
#ifdef __ANDROID__
diff --git a/cmds/svc/src/com/android/commands/svc/DataCommand.java b/cmds/svc/src/com/android/commands/svc/DataCommand.java
index b4dbd1d41bc1..35510cfd38b1 100644
--- a/cmds/svc/src/com/android/commands/svc/DataCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/DataCommand.java
@@ -16,16 +16,12 @@
package com.android.commands.svc;
-/**
- * @deprecated Please use adb shell cmd phone data enabled/disable instead.
- */
-@Deprecated
-public class DataCommand extends Svc.Command {
-
- private static final String DECPRECATED_MESSAGE =
- "adb shell svc data enable/disable is deprecated;"
- + "please use adb shell cmd phone data enable/disable instead.";
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.content.Context;
+import com.android.internal.telephony.ITelephony;
+public class DataCommand extends Svc.Command {
public DataCommand() {
super("data");
}
@@ -37,10 +33,36 @@ public class DataCommand extends Svc.Command {
public String longHelp() {
return shortHelp() + "\n"
+ "\n"
- + DECPRECATED_MESSAGE;
+ + "usage: svc data [enable|disable]\n"
+ + " Turn mobile data on or off.\n\n";
}
public void run(String[] args) {
- System.err.println(DECPRECATED_MESSAGE);
+ boolean validCommand = false;
+ if (args.length >= 2) {
+ boolean flag = false;
+ if ("enable".equals(args[1])) {
+ flag = true;
+ validCommand = true;
+ } else if ("disable".equals(args[1])) {
+ flag = false;
+ validCommand = true;
+ }
+ if (validCommand) {
+ ITelephony phoneMgr
+ = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ try {
+ if (flag) {
+ phoneMgr.enableDataConnectivity();
+ } else
+ phoneMgr.disableDataConnectivity();
+ }
+ catch (RemoteException e) {
+ System.err.println("Mobile data operation failed: " + e);
+ }
+ return;
+ }
+ }
+ System.err.println(longHelp());
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 8b62e2f83cff..d82b151e9ce9 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -17,6 +17,8 @@
package android.accessibilityservice;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
@@ -58,6 +60,8 @@ public final class AccessibilityGestureEvent implements Parcelable {
/** @hide */
@IntDef(prefix = { "GESTURE_" }, value = {
+ GESTURE_DOUBLE_TAP,
+ GESTURE_DOUBLE_TAP_AND_HOLD,
GESTURE_SWIPE_UP,
GESTURE_SWIPE_UP_AND_LEFT,
GESTURE_SWIPE_UP_AND_DOWN,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 47fdcdeafce9..0f619c8d1f76 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -300,6 +300,18 @@ public abstract class AccessibilityService extends Service {
public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
/**
+ * The user has performed a double tap gesture on the touch screen.
+ * @hide
+ */
+ public static final int GESTURE_DOUBLE_TAP = 17;
+
+ /**
+ * The user has performed a double tap and hold gesture on the touch screen.
+ * @hide
+ */
+ public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+ /**
* The {@link Intent} that must be declared as handled by the service.
*/
public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f4e465ab3adb..0f10c3980021 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1943,35 +1943,6 @@ public class AccountManager {
}
/**
- * @hide
- * Removes the shared account.
- * @param account the account to remove
- * @param user the user to remove the account from
- * @return
- */
- public boolean removeSharedAccount(final Account account, UserHandle user) {
- try {
- boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
- return val;
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * @param user
- * @return
- */
- public Account[] getSharedAccounts(UserHandle user) {
- try {
- return mService.getSharedAccountsAsUser(user.getIdentifier());
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Confirms that the user knows the password for an account to make extra
* sure they are the owner of the account. The user-entered password can
* be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 4cf0a2089fe5..012713891d11 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -80,14 +80,11 @@ interface IAccountManager {
String authTokenType);
/* Shared accounts */
- Account[] getSharedAccountsAsUser(int userId);
- boolean removeSharedAccountAsUser(in Account account, int userId);
void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName);
/* Account renaming. */
void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
String getPreviousName(in Account account);
- boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
/* Add account in two steps. */
void startAddAccountSession(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 795f51ac5454..3e6388b4abdc 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1120,8 +1120,8 @@ public class ActivityManager {
}
/**
- * Copies this the values from another TaskDescription, but preserves the hidden fields
- * if they weren't set on {@code other}
+ * Copies values from another TaskDescription, but preserves the hidden fields if they
+ * weren't set on {@code other}. Public fields will be overwritten anyway.
* @hide
*/
public void copyFromPreserveHiddenFields(TaskDescription other) {
@@ -1130,6 +1130,7 @@ public class ActivityManager {
mIconRes = other.mIconRes;
mIconFilename = other.mIconFilename;
mColorPrimary = other.mColorPrimary;
+
if (other.mColorBackground != 0) {
mColorBackground = other.mColorBackground;
}
@@ -1139,12 +1140,20 @@ public class ActivityManager {
if (other.mNavigationBarColor != 0) {
mNavigationBarColor = other.mNavigationBarColor;
}
+
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
- mResizeMode = other.mResizeMode;
- mMinWidth = other.mMinWidth;
- mMinHeight = other.mMinHeight;
+
+ if (other.mResizeMode != RESIZE_MODE_RESIZEABLE) {
+ mResizeMode = other.mResizeMode;
+ }
+ if (other.mMinWidth != -1) {
+ mMinWidth = other.mMinWidth;
+ }
+ if (other.mMinHeight != -1) {
+ mMinHeight = other.mMinHeight;
+ }
}
private TaskDescription(Parcel source) {
@@ -2097,6 +2106,113 @@ public class ActivityManager {
return new TaskSnapshot[size];
}
};
+
+ /** Builder for a {@link TaskSnapshot} object */
+ public static final class Builder {
+ private long mId;
+ private ComponentName mTopActivity;
+ private GraphicBuffer mSnapshot;
+ private ColorSpace mColorSpace;
+ private int mOrientation;
+ private Rect mContentInsets;
+ private boolean mReducedResolution;
+ private float mScaleFraction;
+ private boolean mIsRealSnapshot;
+ private int mWindowingMode;
+ private int mSystemUiVisibility;
+ private boolean mIsTranslucent;
+ private int mPixelFormat;
+
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setTopActivityComponent(ComponentName name) {
+ mTopActivity = name;
+ return this;
+ }
+
+ public Builder setSnapshot(GraphicBuffer buffer) {
+ mSnapshot = buffer;
+ return this;
+ }
+
+ public Builder setColorSpace(ColorSpace colorSpace) {
+ mColorSpace = colorSpace;
+ return this;
+ }
+
+ public Builder setOrientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ public Builder setContentInsets(Rect contentInsets) {
+ mContentInsets = contentInsets;
+ return this;
+ }
+
+ public Builder setReducedResolution(boolean reducedResolution) {
+ mReducedResolution = reducedResolution;
+ return this;
+ }
+
+ public float getScaleFraction() {
+ return mScaleFraction;
+ }
+
+ public Builder setScaleFraction(float scaleFraction) {
+ mScaleFraction = scaleFraction;
+ return this;
+ }
+
+ public Builder setIsRealSnapshot(boolean realSnapshot) {
+ mIsRealSnapshot = realSnapshot;
+ return this;
+ }
+
+ public Builder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ public Builder setSystemUiVisibility(int systemUiVisibility) {
+ mSystemUiVisibility = systemUiVisibility;
+ return this;
+ }
+
+ public Builder setIsTranslucent(boolean isTranslucent) {
+ mIsTranslucent = isTranslucent;
+ return this;
+ }
+
+ public int getPixelFormat() {
+ return mPixelFormat;
+ }
+
+ public Builder setPixelFormat(int pixelFormat) {
+ mPixelFormat = pixelFormat;
+ return this;
+ }
+
+ public TaskSnapshot build() {
+ return new TaskSnapshot(
+ mId,
+ mTopActivity,
+ mSnapshot,
+ mColorSpace,
+ mOrientation,
+ mContentInsets,
+ mReducedResolution,
+ mScaleFraction,
+ mIsRealSnapshot,
+ mWindowingMode,
+ mSystemUiVisibility,
+ mIsTranslucent);
+
+ }
+ }
}
/** @hide */
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 46f88d5c81e4..155e93f9be19 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -99,6 +99,7 @@ import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteOrder;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -251,6 +252,8 @@ class ContextImpl extends Context {
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
+ private File mCratesDir;
+ @GuardedBy("mSync")
private File mNoBackupFilesDir;
@GuardedBy("mSync")
private File mCacheDir;
@@ -702,6 +705,24 @@ class ContextImpl extends Context {
}
@Override
+ public File getCrateDir(@NonNull String crateId) {
+ Preconditions.checkArgument(FileUtils.isValidExtFilename(crateId), "invalidated crateId");
+ final Path cratesRootPath = getDataDir().toPath().resolve("crates");
+ final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
+ .toAbsolutePath().normalize();
+
+ synchronized (mSync) {
+ if (mCratesDir == null) {
+ mCratesDir = cratesRootPath.toFile();
+ }
+ ensurePrivateDirExists(mCratesDir);
+ }
+
+ File cratedDir = absoluteNormalizedCratePath.toFile();
+ return ensurePrivateDirExists(cratedDir);
+ }
+
+ @Override
public File getNoBackupFilesDir() {
synchronized (mSync) {
if (mNoBackupFilesDir == null) {
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 80c9ba2a9c48..eb50581f3319 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1314,8 +1314,8 @@ public class DownloadManager {
// TODO: DownloadProvider.update() should take care of updating corresponding
// MediaProvider entries.
- MediaStore.scanFile(context, before);
- MediaStore.scanFile(context, after);
+ MediaStore.scanFile(mResolver, before);
+ MediaStore.scanFile(mResolver, after);
final ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_TITLE, displayName);
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index f6e9569f513c..cd855cf134d9 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -27,6 +27,7 @@ import android.os.IBinder;
import android.os.IPullAtomCallback;
import android.os.IPullAtomResultReceiver;
import android.os.IStatsCompanionService;
+import android.os.IStatsManagerService;
import android.os.IStatsPullerCallback;
import android.os.IStatsd;
import android.os.RemoteException;
@@ -61,6 +62,9 @@ public final class StatsManager {
@GuardedBy("sLock")
private IStatsCompanionService mStatsCompanion;
+ @GuardedBy("sLock")
+ private IStatsManagerService mStatsManagerService;
+
/**
* Long extra of uid that added the relevant stats config.
*/
@@ -686,6 +690,16 @@ public final class StatsManager {
return mStatsCompanion;
}
+ @GuardedBy("sLock")
+ private IStatsManagerService getIStatsManagerServiceLocked() {
+ if (mStatsManagerService != null) {
+ return mStatsManagerService;
+ }
+ mStatsManagerService = IStatsManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.STATS_MANAGER_SERVICE));
+ return mStatsManagerService;
+ }
+
/**
* Exception thrown when communication with the stats service fails (eg if it is not available).
* This might be thrown early during boot before the stats service has started or if it crashed.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a1305da0a502..ce21db335615 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -152,6 +152,8 @@ import android.os.Vibrator;
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
+import android.os.incremental.IIncrementalManagerNative;
+import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
@@ -1225,6 +1227,20 @@ public final class SystemServiceRegistry {
Context.DATA_LOADER_MANAGER_SERVICE);
return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
}});
+ //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and
+ //IIncrementalManagerNative.aidl, 2) implement the binder interface in
+ //IncrementalManagerService.java, 3) use JNI to call native functions
+ registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class,
+ new CachedServiceFetcher<IncrementalManager>() {
+ @Override
+ public IncrementalManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.INCREMENTAL_SERVICE);
+ if (b == null) {
+ return null;
+ }
+ return new IncrementalManager(
+ IIncrementalManagerNative.Stub.asInterface(b));
+ }});
//CHECKSTYLE:ON IndentationCheck
sInitializing = true;
diff --git a/core/java/android/app/role/RoleControllerService.java b/core/java/android/app/role/RoleControllerService.java
index 06623f93552a..d92c95691eea 100644
--- a/core/java/android/app/role/RoleControllerService.java
+++ b/core/java/android/app/role/RoleControllerService.java
@@ -35,6 +35,7 @@ import android.os.UserHandle;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -82,7 +83,7 @@ public abstract class RoleControllerService extends Service {
public void grantDefaultRoles(RemoteCallback callback) {
enforceCallerSystemUid("grantDefaultRoles");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::grantDefaultRoles, RoleControllerService.this,
@@ -97,7 +98,7 @@ public abstract class RoleControllerService extends Service {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onAddRoleHolder, RoleControllerService.this,
@@ -112,7 +113,7 @@ public abstract class RoleControllerService extends Service {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onRemoveRoleHolder, RoleControllerService.this,
@@ -124,7 +125,7 @@ public abstract class RoleControllerService extends Service {
enforceCallerSystemUid("onClearRoleHolders");
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onClearRoleHolders, RoleControllerService.this,
@@ -146,7 +147,7 @@ public abstract class RoleControllerService extends Service {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
callback.sendResult(qualified ? Bundle.EMPTY : null);
@@ -160,7 +161,7 @@ public abstract class RoleControllerService extends Service {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
boolean visible = onIsApplicationVisibleForRole(roleName, packageName);
callback.sendResult(visible ? Bundle.EMPTY : null);
@@ -171,7 +172,7 @@ public abstract class RoleControllerService extends Service {
enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
boolean visible = onIsRoleVisible(roleName);
callback.sendResult(visible ? Bundle.EMPTY : null);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index bb04a2e52712..61eeacc7fd86 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -42,6 +42,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -284,7 +285,7 @@ public final class RoleManager {
@TestApi
public List<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
try {
return mService.getRoleHoldersAsUser(roleName, user.getIdentifier());
} catch (RemoteException e) {
@@ -321,9 +322,9 @@ public final class RoleManager {
@CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.addRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
createRemoteCallback(executor, callback));
@@ -360,9 +361,9 @@ public final class RoleManager {
@CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.removeRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
createRemoteCallback(executor, callback));
@@ -397,9 +398,9 @@ public final class RoleManager {
@NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
@NonNull Consumer<Boolean> callback) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.clearRoleHoldersAsUser(roleName, flags, user.getIdentifier(),
createRemoteCallback(executor, callback));
@@ -442,9 +443,9 @@ public final class RoleManager {
@TestApi
public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor,
@NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(listener, "listener cannot be null");
- Preconditions.checkNotNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
int userId = user.getIdentifier();
synchronized (mListenersLock) {
ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
@@ -488,8 +489,8 @@ public final class RoleManager {
@TestApi
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
- Preconditions.checkNotNull(listener, "listener cannot be null");
- Preconditions.checkNotNull(user, "user cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
int userId = user.getIdentifier();
synchronized (mListenersLock) {
ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
@@ -529,7 +530,7 @@ public final class RoleManager {
@SystemApi
@TestApi
public void setRoleNamesFromController(@NonNull List<String> roleNames) {
- Preconditions.checkNotNull(roleNames, "roleNames cannot be null");
+ Objects.requireNonNull(roleNames, "roleNames cannot be null");
try {
mService.setRoleNamesFromController(roleNames);
} catch (RemoteException e) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 808f2160109d..85424119e37c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1060,6 +1060,31 @@ public abstract class Context {
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory that is related to the crate on the filesystem.
+ * <p>
+ * The crateId require a validated file name. It can't contain any "..", ".",
+ * {@link File#separatorChar} etc..
+ * </p>
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * </p>
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *</p>
+ *
+ * @param crateId the relative validated file name under {@link Context#getDataDir()}/crates
+ * @return the crate directory file.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ public File getCrateDir(@NonNull String crateId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Returns the absolute path to the directory on the filesystem similar to
* {@link #getFilesDir()}. The difference is that files placed under this
* directory will be excluded from automatic backup to remote storage. See
@@ -4336,6 +4361,15 @@ public abstract class Context {
public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
/**
+ * Use with {@link #getSystemService(String)} to access the
+ * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
+
+ /**
* Official published name of the (internal) permission service.
*
* @see #getSystemService(String)
@@ -4794,6 +4828,12 @@ public abstract class Context {
public static final String INCIDENT_COMPANION_SERVICE = "incidentcompanion";
/**
+ * Service to assist {@link android.app.StatsManager} that lives in system server.
+ * @hide
+ */
+ public static final String STATS_MANAGER_SERVICE = "statsmanager";
+
+ /**
* Service to assist statsd in obtaining general stats.
* @hide
*/
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index d6442e28439f..d1b5135e959d 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -252,6 +252,16 @@ public class ContextWrapper extends Context {
return mBase.getFilesDir();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @NonNull
+ @Override
+ public File getCrateDir(@NonNull String cratedId) {
+ return mBase.getCrateDir(cratedId);
+ }
+
@Override
public File getNoBackupFilesDir() {
return mBase.getNoBackupFilesDir();
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 4178309450bd..ffbca1609557 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -47,7 +47,7 @@ abstract class BaseParceledListSlice<T> implements Parcelable {
* TODO get this number from somewhere else. For now set it to a quarter of
* the 1MB limit.
*/
- private static final int MAX_IPC_SIZE = IBinder.MAX_IPC_SIZE;
+ private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
private final List<T> mList;
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/content/pm/DataLoaderParams.java
index 701f1cc8de02..b163861c53b5 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParams.java
+++ b/core/java/android/content/pm/DataLoaderParams.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,12 +29,12 @@ import java.util.stream.Collectors;
* Hide for now.
* @hide
*/
-public class IncrementalDataLoaderParams {
- @NonNull private final IncrementalDataLoaderParamsParcel mData;
+public class DataLoaderParams {
+ @NonNull private final DataLoaderParamsParcel mData;
- public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName,
+ public DataLoaderParams(@NonNull String url, @NonNull String packageName,
@Nullable Map<String, ParcelFileDescriptor> namedFds) {
- IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel();
+ DataLoaderParamsParcel data = new DataLoaderParamsParcel();
data.staticArgs = url;
data.packageName = packageName;
if (namedFds == null || namedFds.isEmpty()) {
@@ -52,7 +52,7 @@ public class IncrementalDataLoaderParams {
mData = data;
}
- public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) {
+ public DataLoaderParams(@NonNull DataLoaderParamsParcel data) {
mData = data;
}
@@ -70,7 +70,7 @@ public class IncrementalDataLoaderParams {
return mData.packageName;
}
- public final @NonNull IncrementalDataLoaderParamsParcel getData() {
+ public final @NonNull DataLoaderParamsParcel getData() {
return mData;
}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
index cd988dcace5b..33163980b915 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
-import android.os.incremental.NamedParcelFileDescriptor;
+import android.content.pm.NamedParcelFileDescriptor;
/**
* Class for holding data loader configuration parameters.
* @hide
*/
-parcelable IncrementalDataLoaderParamsParcel {
+parcelable DataLoaderParamsParcel {
@utf8InCpp String packageName;
@utf8InCpp String staticArgs;
NamedParcelFileDescriptor[] dynamicArgs;
diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
new file mode 100644
index 000000000000..f00feaeb2f5a
--- /dev/null
+++ b/core/java/android/content/pm/FileSystemControlParcel.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.content.pm;
+
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IncrementalFileSystemControlParcel;
+
+/**
+ * Wraps info needed for DataLoader to provide data.
+ * @hide
+ */
+parcelable FileSystemControlParcel {
+ // Incremental FS control descriptors.
+ @nullable IncrementalFileSystemControlParcel incremental;
+ // Callback-based installation connector.
+ @nullable IPackageInstallerSessionFileSystemConnector callback;
+}
diff --git a/core/java/android/content/pm/IDataLoader.aidl b/core/java/android/content/pm/IDataLoader.aidl
index 60cc9ba9e141..c65bd6acbaf8 100644
--- a/core/java/android/content/pm/IDataLoader.aidl
+++ b/core/java/android/content/pm/IDataLoader.aidl
@@ -30,5 +30,4 @@ oneway interface IDataLoader {
void start(in List<InstallationFile> fileInfos);
void stop();
void destroy();
- void onFileCreated(long inode, in byte[] metadata);
}
diff --git a/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
new file mode 100644
index 000000000000..4b2f29e5ae78
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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 android.content.pm;
+
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstallerSessionFileSystemConnector {
+ void writeData(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
index 038ced1744a1..68dd5f54654b 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.os.ParcelFileDescriptor;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 898631e9a3b1..218c8765f3bb 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -50,8 +50,6 @@ import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -1459,7 +1457,7 @@ public class PackageInstaller {
/** {@hide} */
public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
/** {@hide} */
- public IncrementalDataLoaderParams incrementalParams;
+ public DataLoaderParams incrementalParams;
/** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
* {@hide} */
public String dataLoaderPackageName;
@@ -1496,10 +1494,10 @@ public class PackageInstaller {
isMultiPackage = source.readBoolean();
isStaged = source.readBoolean();
requiredInstalledVersionCode = source.readLong();
- IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
- IncrementalDataLoaderParamsParcel.class.getClassLoader());
+ DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
+ DataLoaderParamsParcel.class.getClassLoader());
if (dataLoaderParamsParcel != null) {
- incrementalParams = new IncrementalDataLoaderParams(
+ incrementalParams = new DataLoaderParams(
dataLoaderParamsParcel);
}
dataLoaderPackageName = source.readString();
@@ -1863,7 +1861,7 @@ public class PackageInstaller {
* {@hide}
*/
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
- public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) {
+ public void setIncrementalParams(@NonNull DataLoaderParams incrementalParams) {
this.incrementalParams = incrementalParams;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 94af5416aa8d..0d1f4046e70e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1513,16 +1513,6 @@ public abstract class PackageManager {
public static final int DELETE_DONT_KILL_APP = 0x00000008;
/**
- * Flag parameter for {@link #deletePackage} to indicate that any
- * contributed media should also be deleted during this uninstall. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
- *
- * @hide
- */
- public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010;
-
- /**
* Flag parameter for {@link #deletePackage} to indicate that package deletion
* should be chatty.
*
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index 515185eaaf57..35df47431a91 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -229,6 +229,11 @@ public interface AndroidPackage extends Parcelable {
String getOverlayTargetName();
+ /**
+ * Map of overlayable name to actor name.
+ */
+ Map<String, String> getOverlayables();
+
// TODO(b/135203078): Does this and getAppInfoPackageName have to be separate methods?
// The refactor makes them the same value with no known consequences, so should be redundant.
String getPackageName();
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 0deb2ab6747b..773231682fe4 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -52,6 +52,7 @@ import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -92,6 +93,7 @@ import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/** @hide */
@@ -287,8 +289,23 @@ public class ApkParseUtils {
+ result.getErrorMessage());
}
- return result.getResultAndNull()
- .setVolumeUuid(volumeUuid)
+ ParsingPackage pkg = result.getResultAndNull();
+ ApkAssets apkAssets = assets.getApkAssets()[0];
+ if (apkAssets.definesOverlayable()) {
+ SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.get(index);
+ Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
+ if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
+ for (String overlayable : overlayableToActor.keySet()) {
+ pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
+ }
+ }
+ }
+ }
+
+ return pkg.setVolumeUuid(volumeUuid)
.setApplicationVolumeUuid(volumeUuid)
.setSigningDetails(SigningDetails.UNKNOWN);
} catch (PackageParserException e) {
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 377279e750c6..0e736d522c10 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -18,6 +18,8 @@ package android.content.pm.parsing;
import static android.os.Build.VERSION_CODES.DONUT;
+import static java.util.Collections.emptyMap;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
@@ -55,11 +57,13 @@ import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.server.SystemConfig;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -126,6 +130,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
private String overlayCategory;
private int overlayPriority;
private boolean overlayIsStatic;
+ private Map<String, String> overlayables = emptyMap();
private String staticSharedLibName;
private long staticSharedLibVersion;
@@ -475,7 +480,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
@Override
public Map<String, ArraySet<PublicKey>> getKeySetMapping() {
- return keySetMapping == null ? Collections.emptyMap() : keySetMapping;
+ return keySetMapping == null ? emptyMap() : keySetMapping;
}
@Override
@@ -773,6 +778,13 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
}
@Override
+ public ParsingPackage addOverlayable(String overlayableName, String actorName) {
+ this.overlayables = CollectionUtils.add(this.overlayables,
+ TextUtils.safeIntern(overlayableName), TextUtils.safeIntern(actorName));
+ return this;
+ }
+
+ @Override
public PackageImpl addAdoptPermission(String adoptPermission) {
this.adoptPermissions = ArrayUtils.add(this.adoptPermissions, adoptPermission);
return this;
@@ -2125,6 +2137,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
}
@Override
+ public Map<String, String> getOverlayables() {
+ return overlayables;
+ }
+
+ @Override
public boolean isOverlayIsStatic() {
return overlayIsStatic;
}
@@ -2291,7 +2308,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
appInfo.metaData = appMetaData;
appInfo.minAspectRatio = minAspectRatio;
appInfo.minSdkVersion = minSdkVersion;
- appInfo.name = name;
+ appInfo.name = className;
if (appInfo.name != null) {
appInfo.name = appInfo.name.trim();
}
@@ -2957,6 +2974,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
dest.writeString(this.overlayCategory);
dest.writeInt(this.overlayPriority);
dest.writeBoolean(this.overlayIsStatic);
+ dest.writeMap(this.overlayables);
dest.writeString(this.staticSharedLibName);
dest.writeLong(this.staticSharedLibVersion);
dest.writeStringList(this.libraryNames);
@@ -3100,6 +3118,8 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
this.overlayCategory = in.readString();
this.overlayPriority = in.readInt();
this.overlayIsStatic = in.readBoolean();
+ this.overlayables = new HashMap<>();
+ in.readMap(overlayables, boot);
this.staticSharedLibName = TextUtils.safeIntern(in.readString());
this.staticSharedLibVersion = in.readLong();
this.libraryNames = in.createStringArrayList();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 43c1f6e335b0..aff1b2e05eaf 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -62,6 +62,8 @@ public interface ParsingPackage extends AndroidPackage {
ParsingPackage addOriginalPackage(String originalPackage);
+ ParsingPackage addOverlayable(String overlayableName, String actorName);
+
ParsingPackage addPermission(ParsedPermission permission);
ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup);
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 48d88678f721..39c1daca1bfd 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -823,6 +823,7 @@ public class SQLiteQueryBuilder {
switch (token.toUpperCase(Locale.US)) {
case "COLLATE": case "ASC": case "DESC":
case "BINARY": case "RTRIM": case "NOCASE":
+ case "LOCALIZED": case "UNICODE":
return;
}
throw new IllegalArgumentException("Invalid token " + token);
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
new file mode 100644
index 000000000000..8231c58a105e
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -0,0 +1,329 @@
+/*
+ * 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 android.hardware.soundtrigger;
+
+import android.hardware.soundtrigger.ModelParams;
+import android.media.AudioFormat;
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+import android.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+/** @hide */
+class ConversionUtil {
+ public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
+ SoundTriggerModuleDescriptor aidlDesc) {
+ SoundTriggerModuleProperties properties = aidlDesc.properties;
+ return new SoundTrigger.ModuleProperties(
+ aidlDesc.handle,
+ properties.implementor,
+ properties.description,
+ properties.uuid,
+ properties.version,
+ properties.maxSoundModels,
+ properties.maxKeyPhrases,
+ properties.maxUsers,
+ aidl2apiRecognitionModes(properties.recognitionModes),
+ properties.captureTransition,
+ properties.maxBufferMs,
+ properties.concurrentCapture,
+ properties.powerConsumptionMw,
+ properties.triggerInEvent
+ );
+ }
+
+ public static int aidl2apiRecognitionModes(int aidlModes) {
+ int result = 0;
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_GENERIC;
+ }
+ return result;
+ }
+
+ public static int api2aidlRecognitionModes(int apiModes) {
+ int result = 0;
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) {
+ result |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) {
+ result |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) {
+ result |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) {
+ result |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return result;
+ }
+
+
+ public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) {
+ return api2aidlSoundModel(apiModel);
+ }
+
+ public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) {
+ SoundModel aidlModel = new SoundModel();
+ aidlModel.type = apiModel.type;
+ aidlModel.uuid = api2aidlUuid(apiModel.uuid);
+ aidlModel.vendorUuid = api2aidlUuid(apiModel.vendorUuid);
+ aidlModel.data = Arrays.copyOf(apiModel.data, apiModel.data.length);
+ return aidlModel;
+ }
+
+ public static String api2aidlUuid(UUID apiUuid) {
+ return apiUuid.toString();
+ }
+
+ public static PhraseSoundModel api2aidlPhraseSoundModel(
+ SoundTrigger.KeyphraseSoundModel apiModel) {
+ PhraseSoundModel aidlModel = new PhraseSoundModel();
+ aidlModel.common = api2aidlSoundModel(apiModel);
+ aidlModel.phrases = new Phrase[apiModel.keyphrases.length];
+ for (int i = 0; i < apiModel.keyphrases.length; ++i) {
+ aidlModel.phrases[i] = api2aidlPhrase(apiModel.keyphrases[i]);
+ }
+ return aidlModel;
+ }
+
+ public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) {
+ Phrase aidlPhrase = new Phrase();
+ aidlPhrase.id = apiPhrase.id;
+ aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes);
+ aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length);
+ aidlPhrase.locale = apiPhrase.locale;
+ aidlPhrase.text = apiPhrase.text;
+ return aidlPhrase;
+ }
+
+ public static RecognitionConfig api2aidlRecognitionConfig(
+ SoundTrigger.RecognitionConfig apiConfig) {
+ RecognitionConfig aidlConfig = new RecognitionConfig();
+ aidlConfig.captureRequested = apiConfig.captureRequested;
+ // apiConfig.allowMultipleTriggers is ignored by the lower layers.
+ aidlConfig.phraseRecognitionExtras =
+ new PhraseRecognitionExtra[apiConfig.keyphrases.length];
+ for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
+ aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
+ apiConfig.keyphrases[i]);
+ }
+ aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ return aidlConfig;
+ }
+
+ public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
+ SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = apiExtra.id;
+ aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes);
+ aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length];
+ for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) {
+ aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]);
+ }
+ return aidlExtra;
+ }
+
+ public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra(
+ PhraseRecognitionExtra aidlExtra) {
+ SoundTrigger.ConfidenceLevel[] apiLevels =
+ new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length];
+ for (int i = 0; i < aidlExtra.levels.length; ++i) {
+ apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id,
+ aidl2apiRecognitionModes(aidlExtra.recognitionModes),
+ aidlExtra.confidenceLevel, apiLevels);
+ }
+
+ public static ConfidenceLevel api2aidlConfidenceLevel(
+ SoundTrigger.ConfidenceLevel apiLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.levelPercent = apiLevel.confidenceLevel;
+ aidlLevel.userId = apiLevel.userId;
+ return aidlLevel;
+ }
+
+ public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel(
+ ConfidenceLevel apiLevel) {
+ return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
+ int modelHandle, RecognitionEvent aidlEvent) {
+ return new SoundTrigger.GenericRecognitionEvent(
+ aidlEvent.status,
+ modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
+ aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
+ int modelHandle,
+ PhraseRecognitionEvent aidlEvent) {
+ SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
+ new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
+ for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
+ apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
+ aidlEvent.common.captureAvailable,
+ aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
+ aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data,
+ apiExtras);
+ }
+
+ public static AudioFormat aidl2apiAudioFormat(AudioConfig audioConfig) {
+ AudioFormat.Builder apiBuilder = new AudioFormat.Builder();
+ apiBuilder.setSampleRate(audioConfig.sampleRateHz);
+ apiBuilder.setChannelMask(aidl2apiChannelInMask(audioConfig.channelMask));
+ apiBuilder.setEncoding(aidl2apiEncoding(audioConfig.format));
+ return apiBuilder.build();
+ }
+
+ public static int aidl2apiEncoding(int aidlFormat) {
+ switch (aidlFormat) {
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_16_BIT:
+ return AudioFormat.ENCODING_PCM_16BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_BIT:
+ return AudioFormat.ENCODING_PCM_8BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_FLOAT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_24_BIT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_24_BIT_PACKED:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_32_BIT:
+ return AudioFormat.ENCODING_PCM_FLOAT;
+
+ case android.media.audio.common.AudioFormat.AC3:
+ return AudioFormat.ENCODING_AC3;
+
+ case android.media.audio.common.AudioFormat.E_AC3:
+ return AudioFormat.ENCODING_E_AC3;
+
+ case android.media.audio.common.AudioFormat.DTS:
+ return AudioFormat.ENCODING_DTS;
+
+ case android.media.audio.common.AudioFormat.DTS_HD:
+ return AudioFormat.ENCODING_DTS_HD;
+
+ case android.media.audio.common.AudioFormat.MP3:
+ return AudioFormat.ENCODING_MP3;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_LC:
+ return AudioFormat.ENCODING_AAC_LC;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V1:
+ return AudioFormat.ENCODING_AAC_HE_V1;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V2:
+ return AudioFormat.ENCODING_AAC_HE_V2;
+
+ case android.media.audio.common.AudioFormat.IEC61937:
+ return AudioFormat.ENCODING_IEC61937;
+
+ case android.media.audio.common.AudioFormat.DOLBY_TRUEHD:
+ return AudioFormat.ENCODING_DOLBY_TRUEHD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_ELD:
+ return AudioFormat.ENCODING_AAC_ELD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_XHE:
+ return AudioFormat.ENCODING_AAC_XHE;
+
+ case android.media.audio.common.AudioFormat.AC4:
+ return AudioFormat.ENCODING_AC4;
+
+ case android.media.audio.common.AudioFormat.E_AC3
+ | android.media.audio.common.AudioFormat.E_AC3_SUB_JOC:
+ return AudioFormat.ENCODING_E_AC3_JOC;
+
+ case android.media.audio.common.AudioFormat.MAT:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_1_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_1:
+ return AudioFormat.ENCODING_DOLBY_MAT;
+
+ case android.media.audio.common.AudioFormat.DEFAULT:
+ return AudioFormat.ENCODING_DEFAULT;
+
+ default:
+ return AudioFormat.ENCODING_INVALID;
+ }
+ }
+
+ public static int api2aidlModelParameter(int apiParam) {
+ switch (apiParam) {
+ case ModelParams.THRESHOLD_FACTOR:
+ return android.media.soundtrigger_middleware.ModelParameter.THRESHOLD_FACTOR;
+ default:
+ return android.media.soundtrigger_middleware.ModelParameter.INVALID;
+ }
+ }
+
+ public static int aidl2apiChannelInMask(int aidlMask) {
+ // We're assuming AudioFormat.CHANNEL_IN_* constants are kept in sync with
+ // android.media.audio.common.AudioChannelMask.
+ return aidlMask;
+ }
+
+ public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange(
+ @Nullable ModelParameterRange aidlRange) {
+ if (aidlRange == null) {
+ return null;
+ }
+ return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 86f3eec4109e..5484df416e79 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,18 +22,29 @@ import static android.system.OsConstants.ENOSYS;
import static android.system.OsConstants.EPERM;
import static android.system.OsConstants.EPIPE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.content.Context;
import android.media.AudioFormat;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.UUID;
/**
@@ -44,6 +55,7 @@ import java.util.UUID;
*/
@SystemApi
public class SoundTrigger {
+ private static final String TAG = "SoundTrigger";
private SoundTrigger() {
}
@@ -119,15 +131,15 @@ public class SoundTrigger {
* recognition callback event */
public final boolean returnsTriggerInEvent;
- ModuleProperties(int id, String implementor, String description,
- String uuid, int version, int maxSoundModels, int maxKeyphrases,
+ ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
+ @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
int maxBufferMs, boolean supportsConcurrentCapture,
int powerConsumptionMw, boolean returnsTriggerInEvent) {
this.id = id;
- this.implementor = implementor;
- this.description = description;
- this.uuid = UUID.fromString(uuid);
+ this.implementor = requireNonNull(implementor);
+ this.description = requireNonNull(description);
+ this.uuid = UUID.fromString(requireNonNull(uuid));
this.version = version;
this.maxSoundModels = maxSoundModels;
this.maxKeyphrases = maxKeyphrases;
@@ -231,6 +243,7 @@ public class SoundTrigger {
/** Unique sound model identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID uuid;
/** Sound model type (e.g. TYPE_KEYPHRASE); */
@@ -238,17 +251,20 @@ public class SoundTrigger {
/** Unique sound model vendor identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID vendorUuid;
/** Opaque data. For use by vendor implementation and enrollment application */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
- public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
- this.uuid = uuid;
- this.vendorUuid = vendorUuid;
+ public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
+ @Nullable byte[] data) {
+ this.uuid = requireNonNull(uuid);
+ this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
this.type = type;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
@Override
@@ -271,8 +287,6 @@ public class SoundTrigger {
if (!(obj instanceof SoundModel))
return false;
SoundModel other = (SoundModel) obj;
- if (!Arrays.equals(data, other.data))
- return false;
if (type != other.type)
return false;
if (uuid == null) {
@@ -285,6 +299,8 @@ public class SoundTrigger {
return false;
} else if (!vendorUuid.equals(other.vendorUuid))
return false;
+ if (!Arrays.equals(data, other.data))
+ return false;
return true;
}
}
@@ -306,24 +322,28 @@ public class SoundTrigger {
/** Locale of the keyphrase. JAVA Locale string e.g en_US */
@UnsupportedAppUsage
+ @NonNull
public final String locale;
/** Key phrase text */
@UnsupportedAppUsage
+ @NonNull
public final String text;
/** Users this key phrase has been trained for. countains sound trigger specific user IDs
* derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
@UnsupportedAppUsage
+ @NonNull
public final int[] users;
@UnsupportedAppUsage
- public Keyphrase(int id, int recognitionModes, String locale, String text, int[] users) {
+ public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text,
+ @Nullable int[] users) {
this.id = id;
this.recognitionModes = recognitionModes;
- this.locale = locale;
- this.text = text;
- this.users = users;
+ this.locale = requireNonNull(locale);
+ this.text = requireNonNull(text);
+ this.users = users != null ? users : new int[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR
@@ -427,13 +447,15 @@ public class SoundTrigger {
public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
/** Key phrases in this sound model */
@UnsupportedAppUsage
+ @NonNull
public final Keyphrase[] keyphrases; // keyword phrases in model
@UnsupportedAppUsage
public KeyphraseSoundModel(
- UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
+ @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
+ @Nullable Keyphrase[] keyphrases) {
super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
- this.keyphrases = keyphrases;
+ this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
@@ -528,7 +550,8 @@ public class SoundTrigger {
};
@UnsupportedAppUsage
- public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
+ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data) {
super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
}
@@ -648,6 +671,12 @@ public class SoundTrigger {
* @hide
*/
public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
+ /**
+ * Generic (non-speech) recognition.
+ *
+ * @hide
+ */
+ public static final int RECOGNITION_MODE_GENERIC = 0x8;
/**
* Status codes for {@link RecognitionEvent}
@@ -739,6 +768,7 @@ public class SoundTrigger {
*
* @hide
*/
+ @NonNull
public final AudioFormat captureFormat;
/**
* Opaque data for use by system applications who know about voice engine internals,
@@ -747,13 +777,14 @@ public class SoundTrigger {
* @hide
*/
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
/** @hide */
@UnsupportedAppUsage
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
@@ -761,8 +792,8 @@ public class SoundTrigger {
this.captureDelayMs = captureDelayMs;
this.capturePreambleMs = capturePreambleMs;
this.triggerInData = triggerInData;
- this.captureFormat = captureFormat;
- this.data = data;
+ this.captureFormat = requireNonNull(captureFormat);
+ this.data = data != null ? data : new byte[0];
}
/**
@@ -965,19 +996,21 @@ public class SoundTrigger {
/** List of all keyphrases in the sound model for which recognition should be performed with
* options for each keyphrase. */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- KeyphraseRecognitionExtra[] keyphrases, byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
- this.keyphrases = keyphrases;
- this.data = data;
+ this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1126,15 +1159,17 @@ public class SoundTrigger {
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
* be recognized (RecognitionConfig) */
@UnsupportedAppUsage
+ @NonNull
public final ConfidenceLevel[] confidenceLevels;
@UnsupportedAppUsage
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
- ConfidenceLevel[] confidenceLevels) {
+ @Nullable ConfidenceLevel[] confidenceLevels) {
this.id = id;
this.recognitionModes = recognitionModes;
this.coarseConfidenceLevel = coarseConfidenceLevel;
- this.confidenceLevels = confidenceLevels;
+ this.confidenceLevels =
+ confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
@@ -1217,16 +1252,18 @@ public class SoundTrigger {
public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable {
/** Indicates if the key phrase is present in the buffered audio available for capture */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra[] keyphraseExtras;
@UnsupportedAppUsage
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data,
- KeyphraseRecognitionExtra[] keyphraseExtras) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
capturePreambleMs, triggerInData, captureFormat, data);
- this.keyphraseExtras = keyphraseExtras;
+ this.keyphraseExtras =
+ keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
@@ -1343,8 +1380,8 @@ public class SoundTrigger {
@UnsupportedAppUsage
public GenericRecognitionEvent(int status, int soundModelHandle,
boolean captureAvailable, int captureSession, int captureDelayMs,
- int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
- byte[] data) {
+ int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+ @Nullable byte[] data) {
super(status, soundModelHandle, captureAvailable, captureSession,
captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
data);
@@ -1408,13 +1445,14 @@ public class SoundTrigger {
/** The updated sound model handle */
public final int soundModelHandle;
/** New sound model data */
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
- SoundModelEvent(int status, int soundModelHandle, byte[] data) {
+ SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
@@ -1498,8 +1536,9 @@ public class SoundTrigger {
* @hide
*/
public static final int SERVICE_STATE_DISABLED = 1;
-
- /**
+ private static Object mServiceLock = new Object();
+ private static ISoundTriggerMiddlewareService mService;
+ /**
* @return returns current package name.
*/
static String getCurrentOpPackageName() {
@@ -1523,25 +1562,22 @@ public class SoundTrigger {
* @hide
*/
@UnsupportedAppUsage
- public static int listModules(ArrayList<ModuleProperties> modules) {
- return listModules(getCurrentOpPackageName(), modules);
+ public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
+ try {
+ SoundTriggerModuleDescriptor[] descs = getService().listModules();
+ modules.clear();
+ modules.ensureCapacity(descs.length);
+ for (SoundTriggerModuleDescriptor desc : descs) {
+ modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
+ }
+ return STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception caught", e);
+ return STATUS_DEAD_OBJECT;
+ }
}
/**
- * Returns a list of descriptors for all hardware modules loaded.
- * @param opPackageName
- * @param modules A ModuleProperties array where the list will be returned.
- * @return - {@link #STATUS_OK} in case of success
- * - {@link #STATUS_ERROR} in case of unspecified error
- * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
- * - {@link #STATUS_NO_INIT} if the native service cannot be reached
- * - {@link #STATUS_BAD_VALUE} if modules is null
- * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
- */
- private static native int listModules(String opPackageName,
- ArrayList<ModuleProperties> modules);
-
- /**
* Get an interface on a hardware module to control sound models and recognition on
* this module.
* @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
@@ -1553,14 +1589,40 @@ public class SoundTrigger {
* @hide
*/
@UnsupportedAppUsage
- public static SoundTriggerModule attachModule(int moduleId,
- StatusListener listener,
- Handler handler) {
- if (listener == null) {
+ public static @NonNull SoundTriggerModule attachModule(int moduleId,
+ @NonNull StatusListener listener,
+ @Nullable Handler handler) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
+ try {
+ return new SoundTriggerModule(getService(), moduleId, listener, looper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
return null;
}
- SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
- return module;
+ }
+
+ private static ISoundTriggerMiddlewareService getService() {
+ synchronized (mServiceLock) {
+ while (true) {
+ IBinder binder = null;
+ try {
+ binder =
+ ServiceManager.getServiceOrThrow(
+ Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
+ binder.linkToDeath(() -> {
+ synchronized (mServiceLock) {
+ mService = null;
+ }
+ }, 0);
+ mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
+ break;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to bind to soundtrigger service", e);
+ }
+ }
+ return mService;
+ }
+
}
/**
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index b16ef5c43346..7cf560019239 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,14 +16,23 @@
package android.hardware.soundtrigger;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
-import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.SoundModel;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-
-import java.lang.ref.WeakReference;
+import android.os.RemoteException;
+import android.util.Log;
/**
* The SoundTriggerModule provides APIs to control sound models and sound detection
@@ -32,39 +41,47 @@ import java.lang.ref.WeakReference;
* @hide
*/
public class SoundTriggerModule {
- @UnsupportedAppUsage
- private long mNativeContext;
-
- @UnsupportedAppUsage
- private int mId;
- private NativeEventHandlerDelegate mEventHandlerDelegate;
+ private static final String TAG = "SoundTriggerModule";
- // to be kept in sync with core/jni/android_hardware_SoundTrigger.cpp
private static final int EVENT_RECOGNITION = 1;
private static final int EVENT_SERVICE_DIED = 2;
- private static final int EVENT_SOUNDMODEL = 3;
- private static final int EVENT_SERVICE_STATE_CHANGE = 4;
+ private static final int EVENT_SERVICE_STATE_CHANGE = 3;
+ @UnsupportedAppUsage
+ private int mId;
+ private EventHandlerDelegate mEventHandlerDelegate;
+ private ISoundTriggerModule mService;
- SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
+ SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
+ int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
+ throws RemoteException {
mId = moduleId;
- mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
- native_setup(SoundTrigger.getCurrentOpPackageName(),
- new WeakReference<SoundTriggerModule>(this));
+ mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
+ mService = service.attach(moduleId, mEventHandlerDelegate);
+ mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
- private native void native_setup(String opPackageName, Object moduleThis);
@Override
protected void finalize() {
- native_finalize();
+ detach();
}
- private native void native_finalize();
/**
* Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
* anymore and associated resources will be released.
- * */
+ * All models must have been unloaded prior to detaching.
+ */
@UnsupportedAppUsage
- public native void detach();
+ public synchronized void detach() {
+ try {
+ if (mService != null) {
+ mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0);
+ mService.detach();
+ mService = null;
+ }
+ } catch (Exception e) {
+ handleException(e);
+ }
+ }
/**
* Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
@@ -82,7 +99,26 @@ public class SoundTriggerModule {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle);
+ public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model,
+ @NonNull int[] soundModelHandle) {
+ try {
+ if (model instanceof SoundTrigger.GenericSoundModel) {
+ SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel(
+ (SoundTrigger.GenericSoundModel) model);
+ soundModelHandle[0] = mService.loadModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ if (model instanceof SoundTrigger.KeyphraseSoundModel) {
+ PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel(
+ (SoundTrigger.KeyphraseSoundModel) model);
+ soundModelHandle[0] = mService.loadPhraseModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ return SoundTrigger.STATUS_BAD_VALUE;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
@@ -97,7 +133,14 @@ public class SoundTriggerModule {
* service fails
*/
@UnsupportedAppUsage
- public native int unloadSoundModel(int soundModelHandle);
+ public synchronized int unloadSoundModel(int soundModelHandle) {
+ try {
+ mService.unloadModel(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
@@ -117,7 +160,16 @@ public class SoundTriggerModule {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int startRecognition(int soundModelHandle, SoundTrigger.RecognitionConfig config);
+ public synchronized int startRecognition(int soundModelHandle,
+ SoundTrigger.RecognitionConfig config) {
+ try {
+ mService.startRecognition(soundModelHandle,
+ ConversionUtil.api2aidlRecognitionConfig(config));
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
@@ -133,12 +185,20 @@ public class SoundTriggerModule {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int stopRecognition(int soundModelHandle);
+ public synchronized int stopRecognition(int soundModelHandle) {
+ try {
+ mService.stopRecognition(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get the current state of a {@link SoundTrigger.SoundModel}.
- * The state will be returned asynchronously as a {@link SoundTrigger#RecognitionEvent}
- * in the callback registered in the {@link SoundTrigger.startRecognition} method.
+ * The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent}
+ * in the callback registered in the
+ * {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method.
* @param soundModelHandle The sound model handle indicating which model's state to return
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
@@ -150,46 +210,71 @@ public class SoundTriggerModule {
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
- public native int getModelState(int soundModelHandle);
+ public synchronized int getModelState(int soundModelHandle) {
+ try {
+ mService.forceRecognitionEvent(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Set a model specific {@link ModelParams} with the given value. This
- * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * parameter will keep its value for the duration the model is loaded regardless of starting
+ * and
* stopping recognition. Once the model is unloaded, the value will be lost.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first before calling this
- * method.
+ * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling
+ * this method.
*
* @param soundModelHandle handle of model to apply parameter
- * @param modelParam {@link ModelParams}
- * @param value Value to set
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
* @return - {@link SoundTrigger#STATUS_OK} in case of success
- * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
- * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
- * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
- * if API is not supported by HAL
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
*/
- public native int setParameter(int soundModelHandle,
- @ModelParams int modelParam, int value);
+ public synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam,
+ int value) {
+ try {
+ mService.setModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam), value);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get a model specific {@link ModelParams}. This parameter will keep its value
* for the duration the model is loaded regardless of starting and stopping recognition.
* Once the model is unloaded, the value will be lost. If the value is not set, a default
* value is returned. See {@link ModelParams} for parameter default values.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first before
+ * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before
* calling this method. Otherwise, an exception can be thrown.
*
* @param soundModelHandle handle of model to get parameter
- * @param modelParam {@link ModelParams}
+ * @param modelParam {@link ModelParams}
* @return value of parameter
* @throws UnsupportedOperationException if hal or model do not support this API.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should
+ * be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should be checked first.
*/
- public native int getParameter(int soundModelHandle,
- @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException;
+ public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ try {
+ return mService.getModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Determine if parameter control is supported for the given model handle.
@@ -197,85 +282,98 @@ public class SoundTriggerModule {
* {@link SoundTriggerModule#getParameter}.
*
* @param soundModelHandle handle of model to get parameter
- * @param modelParam {@link ModelParams}
+ * @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
*/
@Nullable
- public native ModelParamRange queryParameter(int soundModelHandle, @ModelParams int modelParam);
+ public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle,
+ @ModelParams int modelParam) {
+ try {
+ return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport(
+ soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam)));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private int handleException(Exception e) {
+ Log.e(TAG, "", e);
+ if (e instanceof NullPointerException) {
+ return SoundTrigger.STATUS_NO_INIT;
+ }
+ if (e instanceof RemoteException) {
+ return SoundTrigger.STATUS_DEAD_OBJECT;
+ }
+ if (e instanceof IllegalArgumentException) {
+ return SoundTrigger.STATUS_BAD_VALUE;
+ }
+ if (e instanceof IllegalStateException) {
+ return SoundTrigger.STATUS_INVALID_OPERATION;
+ }
+ return SoundTrigger.STATUS_ERROR;
+ }
- private class NativeEventHandlerDelegate {
+ private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
+ IBinder.DeathRecipient {
private final Handler mHandler;
- NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener,
- Handler handler) {
- // find the looper for our new event handler
- Looper looper;
- if (handler != null) {
- looper = handler.getLooper();
- } else {
- looper = Looper.getMainLooper();
- }
+ EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
+ @NonNull Looper looper) {
// construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
case EVENT_RECOGNITION:
- if (listener != null) {
- listener.onRecognition(
- (SoundTrigger.RecognitionEvent)msg.obj);
- }
- break;
- case EVENT_SOUNDMODEL:
- if (listener != null) {
- listener.onSoundModelUpdate(
- (SoundTrigger.SoundModelEvent)msg.obj);
- }
+ listener.onRecognition(
+ (SoundTrigger.RecognitionEvent) msg.obj);
break;
case EVENT_SERVICE_STATE_CHANGE:
- if (listener != null) {
- listener.onServiceStateChange(msg.arg1);
- }
+ listener.onServiceStateChange(msg.arg1);
break;
case EVENT_SERVICE_DIED:
- if (listener != null) {
- listener.onServiceDied();
- }
+ listener.onServiceDied();
break;
default:
+ Log.e(TAG, "Unknown message: " + msg.toString());
break;
- }
}
- };
- } else {
- mHandler = null;
- }
+ }
+ };
}
- Handler handler() {
- return mHandler;
+ @Override
+ public synchronized void onRecognition(int handle, RecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
}
- }
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private static void postEventFromNative(Object module_ref,
- int what, int arg1, int arg2, Object obj) {
- SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get();
- if (module == null) {
- return;
+ @Override
+ public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
}
- NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
- if (delegate != null) {
- Handler handler = delegate.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- handler.sendMessage(m);
- }
+ @Override
+ public synchronized void onRecognitionAvailabilityChange(boolean available)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
+ available ? SoundTrigger.SERVICE_STATE_ENABLED
+ : SoundTrigger.SERVICE_STATE_DISABLED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void binderDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
}
}
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index a47f601033cf..62f0196693ea 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -19,6 +19,7 @@ package android.inputmethodservice;
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -28,6 +29,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.InputChannel;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -39,6 +41,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodSession;
@@ -72,6 +75,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
+ private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
final WeakReference<AbstractInputMethodService> mTarget;
final Context mContext;
@@ -225,6 +229,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
return;
+ case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
+ SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1,
+ (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3);
+ return;
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -267,6 +276,15 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, componentName,
+ autofillId, cb));
+ }
+
+ @BinderThread
+ @Override
public void bindInput(InputBinding binding) {
if (mIsUnbindIssued != null) {
Log.e(TAG, "bindInput must be paired with unbindInput.");
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 156bcfe147f7..7da7dc120dcb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -23,6 +23,8 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
@@ -35,6 +37,7 @@ import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -47,6 +50,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.provider.Settings;
@@ -70,11 +75,14 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
@@ -91,11 +99,14 @@ import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.Collections;
/**
@@ -436,6 +447,14 @@ public class InputMethodService extends AbstractInputMethodService {
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ @Nullable
+ private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
+
+ @Nullable
+ private InlineSuggestionsResponseCallbackImpl mInlineSuggestionsResponseCallback = null;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (isExtractViewShown()) {
@@ -495,6 +514,18 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
+ handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
@@ -670,6 +701,103 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
+ // TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
+ /**
+ * Returns an {@link InlineSuggestionsRequest} to be sent to Autofill.
+ *
+ * <p>Should be implemented by subclasses.</p>
+ */
+ public @Nullable InlineSuggestionsRequest onCreateInlineSuggestionsRequest() {
+ return null;
+ }
+
+ /**
+ * Called when Autofill responds back with {@link InlineSuggestionsResponse} containing
+ * inline suggestions.
+ *
+ * <p>Should be implemented by subclasses.</p>
+ *
+ * @param response {@link InlineSuggestionsResponse} passed back by Autofill.
+ * @return Whether the IME will use and render the inline suggestions.
+ */
+ public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+ return false;
+ }
+
+ /**
+ * Returns whether inline suggestions are enabled on this service.
+ *
+ * TODO(b/137800469): check XML for value.
+ */
+ private boolean isInlineSuggestionsEnabled() {
+ return true;
+ }
+
+ /**
+ * Sends an {@link InlineSuggestionsRequest} obtained from
+ * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
+ * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
+ */
+ private void makeInlineSuggestionsRequest() {
+ if (mInlineSuggestionsRequestInfo == null) {
+ Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
+ return;
+ }
+
+ final IInlineSuggestionsRequestCallback requestCallback =
+ mInlineSuggestionsRequestInfo.mCallback;
+ try {
+ final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
+ if (request == null) {
+ Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+ requestCallback.onInlineSuggestionsUnsupported();
+ } else {
+ if (mInlineSuggestionsResponseCallback == null) {
+ mInlineSuggestionsResponseCallback =
+ new InlineSuggestionsResponseCallbackImpl(this,
+ mInlineSuggestionsRequestInfo.mComponentName,
+ mInlineSuggestionsRequestInfo.mFocusedId);
+ }
+ requestCallback.onInlineSuggestionsRequest(request,
+ mInlineSuggestionsResponseCallback);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+ }
+ }
+
+ private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
+ @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
+ mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
+ callback);
+
+ if (!isInlineSuggestionsEnabled()) {
+ try {
+ callback.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w(TAG, "handleMakeInlineSuggestionsRequest() RemoteException:" + e);
+ }
+ return;
+ }
+
+ if (!mInputStarted) {
+ Log.w(TAG, "onStartInput() not called yet");
+ return;
+ }
+
+ makeInlineSuggestionsRequest();
+ }
+
+ private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
+ @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
+ if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
+ Log.d(TAG, "Response component=" + componentName + " differs from request component="
+ + mInlineSuggestionsRequestInfo.mComponentName + ", ignoring response");
+ return;
+ }
+ onInlineSuggestionsResponse(response);
+ }
+
private void notifyImeHidden() {
setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
@@ -688,6 +816,63 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+ */
+ private static final class InlineSuggestionsResponseCallbackImpl
+ extends IInlineSuggestionsResponseCallback.Stub {
+ private final WeakReference<InputMethodService> mInputMethodService;
+
+ private final ComponentName mRequestComponentName;
+ private final AutofillId mRequestAutofillId;
+
+ private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
+ ComponentName componentName, AutofillId autofillId) {
+ mInputMethodService = new WeakReference<>(inputMethodService);
+ mRequestComponentName = componentName;
+ mRequestAutofillId = autofillId;
+ }
+
+ @Override
+ public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
+ throws RemoteException {
+ final InputMethodService service = mInputMethodService.get();
+ if (service != null) {
+ service.mHandler.sendMessage(obtainMessage(
+ InputMethodService::handleOnInlineSuggestionsResponse, service,
+ mRequestComponentName, mRequestAutofillId, response));
+ }
+ }
+ }
+
+ /**
+ * Information about incoming requests from Autofill Frameworks for inline suggestions.
+ */
+ private static final class InlineSuggestionsRequestInfo {
+ final ComponentName mComponentName;
+ final AutofillId mFocusedId;
+ final IInlineSuggestionsRequestCallback mCallback;
+
+ InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
+ IInlineSuggestionsRequestCallback callback) {
+ this.mComponentName = componentName;
+ this.mFocusedId = focusedId;
+ this.mCallback = callback;
+ }
+
+ /**
+ * Returns whether the cached {@link ComponentName} matches the passed in activity.
+ */
+ public boolean validate(ComponentName componentName) {
+ final boolean result = componentName.equals(mComponentName);
+ if (!result) {
+ Log.d(TAG, "Cached request info ComponentName=" + mComponentName
+ + " differs from received ComponentName=" + componentName);
+ }
+ return result;
+ }
+ }
+
+ /**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
* all of the standard behavior for an input method session.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a92237b9f17f..61da5e67e57b 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -34,6 +34,8 @@ import android.text.TextUtils;
import android.util.Log;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
/**
@@ -528,6 +530,22 @@ public class Environment {
}
/**
+ * Return locations where media files (such as ringtones, notification
+ * sounds, or alarm sounds) may be located on internal storage. These are
+ * typically indexed under {@link MediaStore#VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull Collection<File> getInternalMediaDirectories() {
+ final ArrayList<File> res = new ArrayList<>();
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage directory. This directory may
* not currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 12bce8a305f2..ed980f3049b3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -182,6 +182,14 @@ public interface IBinder {
public static final int MAX_IPC_SIZE = 64 * 1024;
/**
+ * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
+ * buffer limit.
+ */
+ static int getSuggestedMaxIpcSizeBytes() {
+ return MAX_IPC_SIZE;
+ }
+
+ /**
* Get the canonical name of the interface supported by this binder.
*/
public @Nullable String getInterfaceDescriptor() throws RemoteException;
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 1456ff7e6c5e..6b881fecad56 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,6 +24,7 @@ interface IVibratorService
{
boolean hasVibrator();
boolean hasAmplitudeControl();
+ boolean setAlwaysOnEffect(int id, in VibrationEffect effect, in AudioAttributes attributes);
void vibrate(int uid, String opPkg, in VibrationEffect effect, in AudioAttributes attributes,
String reason, IBinder token);
void cancelVibrate(IBinder token);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9eb6445d5931..339397b90afa 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1815,8 +1815,12 @@ public final class Parcel {
p.writeToParcel(this, parcelableFlags);
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Flatten the name of the class of the Parcelable into this Parcel.
+ *
+ * @param p The Parcelable object to be written.
+ * @see #readParcelableCreator
+ */
public final void writeParcelableCreator(@NonNull Parcelable p) {
String name = p.getClass().getName();
writeString(name);
@@ -3011,8 +3015,19 @@ public final class Parcel {
return (T) creator.createFromParcel(this);
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Read and return a Parcelable.Creator from the parcel. The given class loader will be used to
+ * load the {@link Parcelable.Creator}. If it is null, the default class loader will be used.
+ *
+ * @param loader A ClassLoader from which to instantiate the {@link Parcelable.Creator}
+ * object, or null for the default class loader.
+ * @return the previously written {@link Parcelable.Creator}, or null if a null Creator was
+ * written.
+ * @throws BadParcelableException Throws BadParcelableException if there was an error trying to
+ * read the {@link Parcelable.Creator}.
+ *
+ * @see #writeParcelableCreator
+ */
@Nullable
public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
String name = readString();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 725e0fb901f2..5e478b555fca 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,70 +44,10 @@ import java.util.concurrent.Executor;
* <p>
* <b>Device battery life will be significantly affected by the use of this API.</b>
* Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible.
- * </p><p>
- * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
- * This will create a {@link PowerManager.WakeLock} object. You can then use methods
- * on the wake lock object to control the power state of the device.
- * </p><p>
- * In practice it's quite simple:
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
- * wl.acquire();
- * ..screen will stay on during this section..
- * wl.release();
- * }
- * </p><p>
- * The following wake lock levels are defined, with varying effects on system power.
- * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ * possible, and be sure to release them as soon as possible. In most cases,
+ * you'll want to use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
*
- * <table>
- * <tr><th>Flag Value</th>
- * <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
- *
- * <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
- * <td>On*</td> <td>Off</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
- * <td>On</td> <td>Dim</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #FULL_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Bright</td>
- * </tr>
- * </table>
- * </p><p>
- * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
- * display timeouts or the state of the screen and even after the user presses the power button.
- * In all other wake locks, the CPU will run, but the user can still put the device to sleep
- * using the power button.</i>
- * </p><p>
- * In addition, you can add two more flags, which affect behavior of the screen only.
- * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
- *
- * <table>
- * <tr><th>Flag Value</th> <th>Description</th></tr>
- *
- * <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
- * <td>Normal wake locks don't actually turn on the illumination. Instead, they cause
- * the illumination to remain on once it turns on (e.g. from user activity). This flag
- * will force the screen and/or keyboard to turn on immediately, when the WakeLock is
- * acquired. A typical use would be for notifications which are important for the user to
- * see immediately.</td>
- * </tr>
- *
- * <tr><td>{@link #ON_AFTER_RELEASE}</td>
- * <td>If this flag is set, the user activity timer will be reset when the WakeLock is
- * released, causing the illumination to remain on a bit longer. This can be used to
- * reduce flicker if you are cycling between wake lock conditions.</td>
- * </tr>
- * </table>
* <p>
* Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
* permission in an {@code <uses-permission>} element of the application's manifest.
@@ -931,7 +871,8 @@ public final class PowerManager {
* {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
* and {@link #SCREEN_BRIGHT_WAKE_LOCK}. Exactly one wake lock level must be
* specified as part of the {@code levelAndFlags} parameter.
- * </p><p>
+ * </p>
+ * <p>
* The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
* and {@link #ON_AFTER_RELEASE}. Multiple flags can be combined as part of the
* {@code levelAndFlags} parameters.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index ebb2071ead7e..6ae188a8fc3d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -22,10 +22,13 @@ import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.system.Os;
import android.system.OsConstants;
+import android.util.Pair;
import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
+import java.util.Map;
+
/**
* Tools for managing OS processes.
*/
@@ -521,6 +524,8 @@ public class Process {
* @param isTopApp whether the process starts for high priority application.
* @param disabledCompatChanges null-ok list of disabled compat changes for the process being
* started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -541,11 +546,14 @@ public class Process {
@Nullable String packageName,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ true, isTopApp, disabledCompatChanges, zygoteArgs);
+ /*useUsapPool=*/ true, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
}
/** @hide */
@@ -563,10 +571,13 @@ public class Process {
@Nullable String packageName,
@Nullable long[] disabledCompatChanges,
@Nullable String[] zygoteArgs) {
+ // Webview zygote can't access app private data files, so doesn't need to know its data
+ // info.
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges, zygoteArgs);
+ /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges,
+ /* pkgDataInfoMap */ null, zygoteArgs);
}
/**
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index a5188e7cd58d..fbd11ca62a0c 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,6 +70,20 @@ public class SystemVibrator extends Vibrator {
}
@Override
+ public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.setAlwaysOnEffect(id, effect, attributes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect.", e);
+ }
+ return false;
+ }
+
+ @Override
public void vibrate(int uid, String opPkg, VibrationEffect effect,
String reason, AudioAttributes attributes) {
if (mService == null) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 28909c88a734..6456d72a4a6f 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
@@ -152,6 +153,24 @@ public abstract class Vibrator {
public abstract boolean hasAmplitudeControl();
/**
+ * Configure an always-on haptics effect.
+ *
+ * @param id The board-specific always-on ID to configure.
+ * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
+ * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
+ * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+ * vibrations associated with incoming calls. May only be null when effect is null.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
+ @Nullable AudioAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
* Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index d17a5e026880..d32bd26c2bb2 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -39,6 +40,7 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
/*package*/ class ZygoteStartFailedEx extends Exception {
@@ -310,6 +312,8 @@ public class ZygoteProcess {
* started.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @param isTopApp Whether the process starts for high priority application.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
*
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -328,6 +332,8 @@ public class ZygoteProcess {
boolean useUsapPool,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
// TODO (chriswailes): Is there a better place to check this value?
if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -338,7 +344,8 @@ public class ZygoteProcess {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
- packageName, useUsapPool, isTopApp, disabledCompatChanges, zygoteArgs);
+ packageName, useUsapPool, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -539,6 +546,8 @@ public class ZygoteProcess {
* @param packageName null-ok the name of the package this process belongs to.
* @param isTopApp Whether the process starts for high priority application.
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
+ * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
+ * and inode number.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
@@ -559,6 +568,8 @@ public class ZygoteProcess {
boolean useUsapPool,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<>();
@@ -635,6 +646,24 @@ public class ZygoteProcess {
if (isTopApp) {
argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
}
+ if (pkgDataInfoMap != null && pkgDataInfoMap.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.PKG_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : pkgDataInfoMap.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
StringBuilder sb = new StringBuilder();
@@ -1182,12 +1211,14 @@ public class ZygoteProcess {
Process.ProcessStartResult result;
try {
+ // As app zygote is for generating isolated process, at the end it can't access
+ // apps data, so doesn't need to its data info.
result = startViaZygote(processClass, niceName, uid, gid,
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
false /* useUsapPool */, false /* isTopApp */,
- null /* disabledCompatChanges */, extraArgs);
+ null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
index f84d7efe06b1..17a310a5beb0 100644
--- a/core/java/android/os/incremental/IIncrementalManager.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -16,8 +16,8 @@
package android.os.incremental;
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.IDataLoaderStatusListener;
/**
@@ -27,8 +27,8 @@ import android.content.pm.IDataLoaderStatusListener;
*/
interface IIncrementalManager {
boolean prepareDataLoader(int mountId,
- in IncrementalFileSystemControlParcel control,
- in IncrementalDataLoaderParamsParcel params,
+ in FileSystemControlParcel control,
+ in DataLoaderParamsParcel params,
in IDataLoaderStatusListener listener);
boolean startDataLoader(int mountId);
void showHealthBlockedUI(int mountId);
diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
index d9c7c6b5cc21..14215b1ea84d 100644
--- a/core/java/android/os/incremental/IIncrementalManagerNative.aidl
+++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
@@ -16,7 +16,7 @@
package android.os.incremental;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.DataLoaderParamsParcel;
/** @hide */
interface IIncrementalManagerNative {
@@ -32,7 +32,7 @@ interface IIncrementalManagerNative {
* Opens or creates a storage given a target path and data loader params. Returns the storage ID.
*/
int openStorage(in @utf8InCpp String path);
- int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
+ int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode);
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 5bd0748b8d97..fb94fc9bd719 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -35,6 +35,7 @@ import static dalvik.system.VMRuntime.getInstructionSet;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.os.IVold;
import android.os.RemoteException;
@@ -82,12 +83,12 @@ public final class IncrementalFileStorages {
public IncrementalFileStorages(@NonNull String packageName,
@NonNull File stageDir,
@NonNull IncrementalManager incrementalManager,
- @NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) {
+ @NonNull DataLoaderParams dataLoaderParams) {
mPackageName = packageName;
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
- if (incrementalDataLoaderParams.getPackageName().equals("local")) {
- final String incrementalPath = incrementalDataLoaderParams.getStaticArgs();
+ if (dataLoaderParams.getPackageName().equals("local")) {
+ final String incrementalPath = dataLoaderParams.getStaticArgs();
mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
mDefaultDir = incrementalPath;
return;
@@ -97,7 +98,7 @@ public final class IncrementalFileStorages {
return;
}
mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
- incrementalDataLoaderParams,
+ dataLoaderParams,
IncrementalManager.CREATE_MODE_CREATE
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
}
@@ -265,7 +266,7 @@ public final class IncrementalFileStorages {
}
private String getTempDir() {
- final String tmpDirRoot = "/data/tmp";
+ final String tmpDirRoot = "/data/incremental/tmp";
final Random random = new Random();
final Path tmpDir =
Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1)));
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index c30f5589a835..c72228708b4c 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.content.pm.DataLoaderParams;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
@@ -104,7 +105,7 @@ public final class IncrementalManager {
*/
@Nullable
public IncrementalStorage createStorage(@NonNull String path,
- @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
+ @NonNull DataLoaderParams params, @CreateMode int createMode,
boolean autoStartDataLoader) {
try {
final int id = mNativeService.createStorage(path, params.getData(), createMode);
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 62603fee1137..2e9f27e74544 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -264,6 +264,8 @@ public class StorageManager {
public static final int FLAG_REAL_STATE = 1 << 9;
/** {@hide} */
public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_RECENT = 1 << 11;
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1125,7 +1127,7 @@ public class StorageManager {
* Return the {@link StorageVolume} that contains the given file, or
* {@code null} if none.
*/
- public @Nullable StorageVolume getStorageVolume(File file) {
+ public @Nullable StorageVolume getStorageVolume(@NonNull File file) {
return getStorageVolume(getVolumeList(), file);
}
@@ -1140,7 +1142,7 @@ public class StorageManager {
return getPrimaryStorageVolume();
default:
for (StorageVolume vol : getStorageVolumes()) {
- if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
+ if (Objects.equals(vol.getMediaStoreVolumeName(), volumeName)) {
return vol;
}
}
@@ -1201,12 +1203,13 @@ public class StorageManager {
}
/**
- * Return the list of shared/external storage volumes available to the
- * current user. This includes both the primary shared storage device and
- * any attached external volumes including SD cards and USB drives.
- *
- * @see Environment#getExternalStorageDirectory()
- * @see StorageVolume#createAccessIntent(String)
+ * Return the list of shared/external storage volumes currently available to
+ * the calling user.
+ * <p>
+ * These storage volumes are actively attached to the device, but may be in
+ * any mount state, as returned by {@link StorageVolume#getState()}. Returns
+ * both the primary shared storage device and any attached external volumes,
+ * including SD cards and USB drives.
*/
public @NonNull List<StorageVolume> getStorageVolumes() {
final ArrayList<StorageVolume> res = new ArrayList<>();
@@ -1216,6 +1219,22 @@ public class StorageManager {
}
/**
+ * Return the list of shared/external storage volumes both currently and
+ * recently available to the calling user.
+ * <p>
+ * Recently available storage volumes are likely to reappear in the future,
+ * so apps are encouraged to preserve any indexed metadata related to these
+ * volumes to optimize user experiences.
+ */
+ public @NonNull List<StorageVolume> getRecentStorageVolumes() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_RECENT));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage volume available to the
* current user. This volume is the same storage device returned by
* {@link Environment#getExternalStorageDirectory()} and
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index aefe8430f9de..560d6171d5ee 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -29,6 +29,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.DocumentsContract;
+import android.provider.MediaStore;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -173,7 +174,7 @@ public final class StorageVolume implements Parcelable {
* @return the mount path
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
@TestApi
public String getPath() {
return mPath.toString();
@@ -190,12 +191,35 @@ public final class StorageVolume implements Parcelable {
}
/** {@hide} */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
public File getPathFile() {
return mPath;
}
/**
+ * Returns the directory where this volume is currently mounted.
+ * <p>
+ * Direct filesystem access via this path has significant emulation
+ * overhead, and apps are instead strongly encouraged to interact with media
+ * on storage volumes via the {@link MediaStore} APIs.
+ * <p>
+ * This directory does not give apps any additional access beyond what they
+ * already have via {@link MediaStore}.
+ *
+ * @return directory where this volume is mounted, or {@code null} if the
+ * volume is not currently mounted.
+ */
+ public @Nullable File getDirectory() {
+ switch (mState) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY:
+ return mPath;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Returns a user-visible description of the volume.
*
* @return the volume description
@@ -265,6 +289,24 @@ public final class StorageVolume implements Parcelable {
return mFsUuid;
}
+ /**
+ * Return the volume name that can be used to interact with this storage
+ * device through {@link MediaStore}.
+ *
+ * @return opaque volume name, or {@code null} if this volume is not indexed
+ * by {@link MediaStore}.
+ * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Video.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Images.Media#getContentUri(String)
+ */
+ public @Nullable String getMediaStoreVolumeName() {
+ if (isPrimary()) {
+ return MediaStore.VOLUME_EXTERNAL_PRIMARY;
+ } else {
+ return getNormalizedUuid();
+ }
+ }
+
/** {@hide} */
public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1a794ebf2a59..99b45d60c319 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -17,14 +17,18 @@
package android.os.storage;
import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.DebugUtils;
import android.util.TimeUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import java.io.File;
import java.util.Locale;
import java.util.Objects;
@@ -92,6 +96,27 @@ public class VolumeRecord implements Parcelable {
return (userFlags & USER_FLAG_SNOOZED) != 0;
}
+ public StorageVolume buildStorageVolume(Context context) {
+ final String id = "unknown:" + fsUuid;
+ final File userPath = new File("/dev/null");
+ final File internalPath = new File("/dev/null");
+ final boolean primary = false;
+ final boolean removable = true;
+ final boolean emulated = false;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(UserHandle.USER_NULL);
+ final String envState = Environment.MEDIA_UNKNOWN;
+
+ String description = nickname;
+ if (description == null) {
+ description = context.getString(android.R.string.unknownName);
+ }
+
+ return new StorageVolume(id, userPath, internalPath, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, fsUuid, envState);
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("VolumeRecord:");
pw.increaseIndent();
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
index 00c9e72df880..b216e2b7ed8d 100644
--- a/core/java/android/provider/BaseColumns.java
+++ b/core/java/android/provider/BaseColumns.java
@@ -16,13 +16,11 @@
package android.provider;
-import android.database.Cursor;
-
public interface BaseColumns {
/**
* The unique ID for a row.
*/
- @Column(Cursor.FIELD_TYPE_INTEGER)
+ // @Column(Cursor.FIELD_TYPE_INTEGER)
public static final String _ID = "_id";
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2fa3386bccb8..63204d36f396 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -21,17 +21,16 @@ import android.annotation.CurrentTimeMillisLong;
import android.annotation.CurrentTimeSecondsLong;
import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
-import android.app.AppGlobals;
+import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@@ -44,39 +43,28 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
-import android.graphics.Point;
import android.graphics.PostProcessor;
import android.media.ExifInterface;
-import android.media.MediaFile;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.OperationCanceledException;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.os.storage.VolumeInfo;
-import android.os.storage.VolumeRecord;
-import android.service.media.CameraPrewarmService;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
+import android.util.Size;
import libcore.util.HexEncoding;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -86,6 +74,7 @@ import java.lang.annotation.RetentionPolicy;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -160,44 +149,34 @@ public final class MediaStore {
/** {@hide} */
public static final String SCAN_VOLUME_CALL = "scan_volume";
/** {@hide} */
- public static final String SUICIDE_CALL = "suicide";
-
- /**
- * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
- * the file path originated from shell.
- *
- * {@hide}
- */
- public static final String EXTRA_ORIGINATED_FROM_SHELL =
- "android.intent.extra.originated_from_shell";
-
- /**
- * The method name used by the media scanner and mtp to tell the media provider to
- * rescan and reclassify that have become unhidden because of renaming folders or
- * removing nomedia files
- * @hide
- */
- @Deprecated
- public static final String UNHIDE_CALL = "unhide";
+ public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request";
+ /** {@hide} */
+ public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request";
+ /** {@hide} */
+ public static final String CREATE_FAVORITE_REQUEST_CALL = "create_favorite_request";
+ /** {@hide} */
+ public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request";
- /**
- * The method name used by the media scanner service to reload all localized ringtone titles due
- * to a locale change.
- * @hide
- */
- public static final String RETRANSLATE_CALL = "update_titles";
/** {@hide} */
public static final String GET_VERSION_CALL = "get_version";
+
/** {@hide} */
public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
/** {@hide} */
public static final String GET_MEDIA_URI_CALL = "get_media_uri";
/** {@hide} */
- public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media";
+ public static final String EXTRA_URI = "uri";
/** {@hide} */
- public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
+ public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
+
+ /** {@hide} */
+ public static final String EXTRA_CLIP_DATA = "clip_data";
+ /** {@hide} */
+ public static final String EXTRA_CONTENT_VALUES = "content_values";
+ /** {@hide} */
+ public static final String EXTRA_RESULT = "result";
/**
* This is for internal use by the media scanner only.
@@ -373,10 +352,10 @@ public final class MediaStore {
* service.
* <p>
* This meta-data should reference the fully qualified class name of the prewarm service
- * extending {@link CameraPrewarmService}.
+ * extending {@code CameraPrewarmService}.
* <p>
* The prewarm service will get bound and receive a prewarm signal
- * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
+ * {@code CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
* An application implementing a prewarm service should do the absolute minimum amount of work
* to initialize the camera in order to reduce startup time in likely case that shortly after a
* camera launch intent would be sent.
@@ -606,6 +585,10 @@ public final class MediaStore {
* {@link ContentResolver#delete}.
* <p>
* By default, trashed items are filtered away from operations.
+ *
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+ * @see MediaStore#createTrashRequest
*/
@Match
public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed";
@@ -620,6 +603,10 @@ public final class MediaStore {
* <p>
* By default, favorite items are <em>not</em> filtered away from
* operations.
+ *
+ * @see MediaColumns#IS_FAVORITE
+ * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+ * @see MediaStore#createFavoriteRequest
*/
@Match
public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
@@ -749,197 +736,6 @@ public final class MediaStore {
}
/**
- * Create a new pending media item using the given parameters. Pending items
- * are expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @return token which can be passed to {@link #openPending(Context, Uri)}
- * to work with this pending item.
- * @see MediaColumns#IS_PENDING
- * @see MediaStore#setIncludePending(Uri)
- * @see MediaStore#createPending(Context, PendingParams)
- * @removed
- */
- @Deprecated
- public static @NonNull Uri createPending(@NonNull Context context,
- @NonNull PendingParams params) {
- return context.getContentResolver().insert(params.insertUri, params.insertValues);
- }
-
- /**
- * Open a pending media item to make progress on it. You can open a pending
- * item multiple times before finally calling either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
- *
- * @param uri token which was previously returned from
- * {@link #createPending(Context, PendingParams)}.
- * @removed
- */
- @Deprecated
- public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
- return new PendingSession(context, uri);
- }
-
- /**
- * Parameters that describe a pending media item.
- *
- * @removed
- */
- @Deprecated
- public static class PendingParams {
- /** {@hide} */
- public final Uri insertUri;
- /** {@hide} */
- public final ContentValues insertValues;
-
- /**
- * Create parameters that describe a pending media item.
- *
- * @param insertUri the {@code content://} Uri where this pending item
- * should be inserted when finally published. For example, to
- * publish an image, use
- * {@link MediaStore.Images.Media#getContentUri(String)}.
- */
- public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
- @NonNull String mimeType) {
- this.insertUri = Objects.requireNonNull(insertUri);
- final long now = System.currentTimeMillis() / 1000;
- this.insertValues = new ContentValues();
- this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
- this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
- this.insertValues.put(MediaColumns.DATE_ADDED, now);
- this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
- this.insertValues.put(MediaColumns.IS_PENDING, 1);
- this.insertValues.put(MediaColumns.DATE_EXPIRES,
- (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
- }
-
- public void setRelativePath(@Nullable String relativePath) {
- if (relativePath == null) {
- this.insertValues.remove(MediaColumns.RELATIVE_PATH);
- } else {
- this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath);
- }
- }
-
- /**
- * Optionally set the Uri from where the file has been downloaded. This is used
- * for files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#DOWNLOAD_URI
- */
- public void setDownloadUri(@Nullable Uri downloadUri) {
- if (downloadUri == null) {
- this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
- } else {
- this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
- }
- }
-
- /**
- * Optionally set the Uri indicating HTTP referer of the file. This is used for
- * files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#REFERER_URI
- */
- public void setRefererUri(@Nullable Uri refererUri) {
- if (refererUri == null) {
- this.insertValues.remove(DownloadColumns.REFERER_URI);
- } else {
- this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
- }
- }
- }
-
- /**
- * Session actively working on a pending media item. Pending items are
- * expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @removed
- */
- @Deprecated
- public static class PendingSession implements AutoCloseable {
- /** {@hide} */
- private final Context mContext;
- /** {@hide} */
- private final Uri mUri;
-
- /** {@hide} */
- public PendingSession(Context context, Uri uri) {
- mContext = Objects.requireNonNull(context);
- mUri = Objects.requireNonNull(uri);
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
- return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link OutputStream#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
- return mContext.getContentResolver().openOutputStream(mUri);
- }
-
- /**
- * Notify of current progress on this pending media item. Gallery
- * applications may choose to surface progress information of this
- * pending item.
- *
- * @param progress a percentage between 0 and 100.
- */
- public void notifyProgress(@IntRange(from = 0, to = 100) int progress) {
- final Uri withProgress = mUri.buildUpon()
- .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build();
- mContext.getContentResolver().notifyChange(withProgress, null, 0);
- }
-
- /**
- * When this media item is successfully completed, call this method to
- * publish and make the final item visible to the user.
- *
- * @return the final {@code content://} Uri representing the newly
- * published media.
- */
- public @NonNull Uri publish() {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_PENDING, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
- mContext.getContentResolver().update(mUri, values, null, null);
- return mUri;
- }
-
- /**
- * When this media item has failed to be completed, call this method to
- * destroy the pending item record and any data related to it.
- */
- public void abandon() {
- mContext.getContentResolver().delete(mUri, null, null);
- }
-
- @Override
- public void close() {
- // No resources to close, but at least we can inform people that no
- // progress is being actively made.
- notifyProgress(-1);
- }
- }
-
- /**
* Mark the given item as being "trashed", meaning it should be deleted at
* some point in the future. This is a more gentle operation than simply
* calling {@link ContentResolver#delete(Uri, String, String[])}, which
@@ -952,7 +748,9 @@ public final class MediaStore {
* @see MediaStore#setIncludeTrashed(Uri)
* @see MediaStore#trash(Context, Uri)
* @see MediaStore#untrash(Context, Uri)
+ * @removed
*/
+ @Deprecated
public static void trash(@NonNull Context context, @NonNull Uri uri) {
trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
}
@@ -970,7 +768,9 @@ public final class MediaStore {
* @see MediaStore#setIncludeTrashed(Uri)
* @see MediaStore#trash(Context, Uri)
* @see MediaStore#untrash(Context, Uri)
+ * @removed
*/
+ @Deprecated
public static void trash(@NonNull Context context, @NonNull Uri uri,
@DurationMillisLong long timeoutMillis) {
if (timeoutMillis < 0) {
@@ -992,7 +792,9 @@ public final class MediaStore {
* @see MediaStore#setIncludeTrashed(Uri)
* @see MediaStore#trash(Context, Uri)
* @see MediaStore#untrash(Context, Uri)
+ * @removed
*/
+ @Deprecated
public static void untrash(@NonNull Context context, @NonNull Uri uri) {
final ContentValues values = new ContentValues();
values.put(MediaColumns.IS_TRASHED, 0);
@@ -1010,6 +812,180 @@ public final class MediaStore {
return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build();
}
+ private static @NonNull PendingIntent createRequest(@NonNull ContentResolver resolver,
+ @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values) {
+ Objects.requireNonNull(resolver);
+ Objects.requireNonNull(uris);
+
+ final Iterator<Uri> it = uris.iterator();
+ final ClipData clipData = ClipData.newRawUri(null, it.next());
+ while (it.hasNext()) {
+ clipData.addItem(new ClipData.Item(it.next()));
+ }
+
+ final Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_CLIP_DATA, clipData);
+ extras.putParcelable(EXTRA_CONTENT_VALUES, values);
+ return resolver.call(AUTHORITY, method, null, extras).getParcelable(EXTRA_RESULT);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to grant your
+ * app write access for the requested media items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * Permissions granted through this mechanism are tied to the lifecycle of
+ * the {@link Activity} that requests them. If you need to retain
+ * longer-term access for background actions, you can place items into a
+ * {@link ClipData} or {@link Intent} which can then be passed to
+ * {@link Context#startService} or
+ * {@link android.app.job.JobInfo.Builder#setClipData}. Be sure to include
+ * any relevant access modes you want to retain, such as
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ * <p>
+ * For security and performance reasons this method does not support
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} or
+ * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ */
+ public static @NonNull PendingIntent createWriteRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris) {
+ return createRequest(resolver, CREATE_WRITE_REQUEST_CALL, uris, null);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to trash the
+ * requested media items. When the user approves this request,
+ * {@link MediaColumns#IS_TRASHED} is set on these items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ * @param value The {@link MediaColumns#IS_TRASHED} value to apply.
+ * @see MediaColumns#IS_TRASHED
+ * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+ */
+ public static @NonNull PendingIntent createTrashRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris, boolean value) {
+ final ContentValues values = new ContentValues();
+ if (value) {
+ values.put(MediaColumns.IS_TRASHED, 1);
+ values.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DateUtils.WEEK_IN_MILLIS) / 1000);
+ } else {
+ values.put(MediaColumns.IS_TRASHED, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ }
+ return createRequest(resolver, CREATE_TRASH_REQUEST_CALL, uris, values);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to favorite the
+ * requested media items. When the user approves this request,
+ * {@link MediaColumns#IS_FAVORITE} is set on these items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ * @param value The {@link MediaColumns#IS_FAVORITE} value to apply.
+ * @see MediaColumns#IS_FAVORITE
+ * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+ */
+ public static @NonNull PendingIntent createFavoriteRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris, boolean value) {
+ final ContentValues values = new ContentValues();
+ if (value) {
+ values.put(MediaColumns.IS_FAVORITE, 1);
+ } else {
+ values.put(MediaColumns.IS_FAVORITE, 0);
+ }
+ return createRequest(resolver, CREATE_FAVORITE_REQUEST_CALL, uris, values);
+ }
+
+ /**
+ * Create a {@link PendingIntent} that will prompt the user to permanently
+ * delete the requested media items. When the user approves this request,
+ * {@link ContentResolver#delete} will be called on these items.
+ * <p>
+ * This call only generates the request for a prompt; to display the prompt,
+ * call {@link Activity#startIntentSenderForResult} with
+ * {@link PendingIntent#getIntentSender()}. You can then determine if the
+ * user granted your request by testing for {@link Activity#RESULT_OK} in
+ * {@link Activity#onActivityResult}.
+ * <p>
+ * The displayed prompt will reflect all the media items you're requesting,
+ * including those for which you already hold write access. If you want to
+ * determine if you already hold write access before requesting access, use
+ * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+ *
+ * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+ * Typically this value is {@link Context#getContentResolver()},
+ * but if you need more explicit lifecycle controls, you can
+ * obtain a {@link ContentProviderClient} and wrap it using
+ * {@link ContentResolver#wrap(ContentProviderClient)}.
+ * @param uris The set of media items to include in this request. Each item
+ * must be hosted by {@link MediaStore#AUTHORITY} and must
+ * reference a specific media item by {@link BaseColumns#_ID}.
+ */
+ public static @NonNull PendingIntent createDeleteRequest(@NonNull ContentResolver resolver,
+ @NonNull Collection<Uri> uris) {
+ return createRequest(resolver, CREATE_DELETE_REQUEST_CALL, uris, null);
+ }
+
/**
* Common media metadata columns.
*/
@@ -1127,9 +1103,9 @@ public final class MediaStore {
* Trashed items are retained until they expire as defined by
* {@link #DATE_EXPIRES}.
*
+ * @see MediaColumns#IS_TRASHED
* @see MediaStore#QUERY_ARG_MATCH_TRASHED
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
+ * @see MediaStore#createTrashRequest
*/
@Column(Cursor.FIELD_TYPE_INTEGER)
public static final String IS_TRASHED = "is_trashed";
@@ -1302,7 +1278,9 @@ public final class MediaStore {
* Flag indicating if the media item has been marked as being a
* "favorite" by the user.
*
+ * @see MediaColumns#IS_FAVORITE
* @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+ * @see MediaStore#createFavoriteRequest
*/
@Column(Cursor.FIELD_TYPE_INTEGER)
public static final String IS_FAVORITE = "is_favorite";
@@ -1493,34 +1471,22 @@ public final class MediaStore {
return ContentUris.withAppendedId(getContentUri(volumeName), rowId);
}
- /**
- * For use only by the MTP implementation.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static Uri getMtpObjectsUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build();
+ public static Uri getMtpObjectsUri(@NonNull String volumeName) {
+ return MediaStore.Files.getContentUri(volumeName);
}
- /**
- * For use only by the MTP implementation.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static final Uri getMtpObjectsUri(String volumeName,
- long fileId) {
- return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId);
+ public static final Uri getMtpObjectsUri(@NonNull String volumeName, long fileId) {
+ return MediaStore.Files.getContentUri(volumeName, fileId);
}
- /**
- * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
- * @hide
- */
+ /** {@hide} */
@UnsupportedAppUsage
- public static final Uri getMtpReferencesUri(String volumeName,
- long fileId) {
- return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references")
- .build();
+ public static final Uri getMtpReferencesUri(@NonNull String volumeName, long fileId) {
+ return MediaStore.Files.getContentUri(volumeName, fileId);
}
/**
@@ -1643,9 +1609,21 @@ public final class MediaStore {
public static final int FULL_SCREEN_KIND = 2;
public static final int MICRO_KIND = 3;
- public static final Point MINI_SIZE = new Point(512, 384);
- public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
- public static final Point MICRO_SIZE = new Point(96, 96);
+ public static final Size MINI_SIZE = new Size(512, 384);
+ public static final Size FULL_SCREEN_SIZE = new Size(1024, 786);
+ public static final Size MICRO_SIZE = new Size(96, 96);
+
+ public static @NonNull Size getKindSize(int kind) {
+ if (kind == ThumbnailConstants.MICRO_KIND) {
+ return ThumbnailConstants.MICRO_SIZE;
+ } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
+ return ThumbnailConstants.FULL_SCREEN_SIZE;
+ } else if (kind == ThumbnailConstants.MINI_KIND) {
+ return ThumbnailConstants.MINI_SIZE;
+ } else {
+ throw new IllegalArgumentException("Unsupported kind: " + kind);
+ }
+ }
}
/**
@@ -1749,22 +1727,22 @@ public final class MediaStore {
}
}
- /** {@hide} */
+ /**
+ * @deprecated since this method doesn't have a {@link Context}, we can't
+ * find the actual {@link StorageVolume} for the given path, so
+ * only a vague guess is returned. Callers should use
+ * {@link StorageManager#getStorageVolume(File)} instead.
+ * @hide
+ */
+ @Deprecated
public static @NonNull String getVolumeName(@NonNull File path) {
- if (FileUtils.contains(Environment.getStorageDirectory(), path)) {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- final StorageVolume sv = sm.getStorageVolume(path);
- if (sv != null) {
- if (sv.isPrimary()) {
- return VOLUME_EXTERNAL_PRIMARY;
- } else {
- return checkArgumentVolumeName(sv.getNormalizedUuid());
- }
- }
- throw new IllegalStateException("Unknown volume at " + path);
+ // Ideally we'd find the relevant StorageVolume, but we don't have a
+ // Context to obtain it from, so the best we can do is assume
+ if (path.getAbsolutePath()
+ .startsWith(Environment.getStorageDirectory().getAbsolutePath())) {
+ return MediaStore.VOLUME_EXTERNAL;
} else {
- return VOLUME_INTERNAL;
+ return MediaStore.VOLUME_INTERNAL;
}
}
@@ -1777,7 +1755,7 @@ public final class MediaStore {
/**
* Currently outstanding thumbnail requests that can be cancelled.
*/
- @GuardedBy("sPending")
+ // @GuardedBy("sPending")
private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>();
/**
@@ -1789,16 +1767,7 @@ public final class MediaStore {
@Deprecated
static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
int kind, @Nullable BitmapFactory.Options opts) {
- final Point size;
- if (kind == ThumbnailConstants.MICRO_KIND) {
- size = ThumbnailConstants.MICRO_SIZE;
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- size = ThumbnailConstants.FULL_SCREEN_SIZE;
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- size = ThumbnailConstants.MINI_SIZE;
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ final Size size = ThumbnailConstants.getKindSize(kind);
CancellationSignal signal = null;
synchronized (sPending) {
@@ -1810,7 +1779,7 @@ public final class MediaStore {
}
try {
- return cr.loadThumbnail(uri, Point.convert(size), signal);
+ return cr.loadThumbnail(uri, size, signal);
} catch (IOException e) {
Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
return null;
@@ -2007,26 +1976,14 @@ public final class MediaStore {
@Deprecated
public static final String insertImage(ContentResolver cr, String imagePath,
String name, String description) throws FileNotFoundException {
- final File file = new File(imagePath);
- final String mimeType = MediaFile.getMimeTypeForFile(imagePath);
-
- if (TextUtils.isEmpty(name)) name = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType);
-
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (InputStream in = new FileInputStream(file);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
- return session.publish().toString();
- } catch (Exception e) {
- Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
- return null;
+ final Bitmap source;
+ try {
+ source = ImageDecoder
+ .decodeBitmap(ImageDecoder.createSource(new File(imagePath)));
+ } catch (IOException e) {
+ throw new FileNotFoundException(e.getMessage());
}
+ return insertImage(cr, source, name, description);
}
/**
@@ -2043,22 +2000,34 @@ public final class MediaStore {
* control over lifecycle.
*/
@Deprecated
- public static final String insertImage(ContentResolver cr, Bitmap source,
- String title, String description) {
+ public static final String insertImage(ContentResolver cr, Bitmap source, String title,
+ String description) {
if (TextUtils.isEmpty(title)) title = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg");
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (OutputStream out = session.openOutputStream()) {
+ final long now = System.currentTimeMillis();
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DISPLAY_NAME, title);
+ values.put(MediaColumns.MIME_TYPE, "image/jpeg");
+ values.put(MediaColumns.DATE_ADDED, now / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, now / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (now + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
+
+ final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ try {
+ try (OutputStream out = cr.openOutputStream(uri)) {
source.compress(Bitmap.CompressFormat.JPEG, 90, out);
}
- return session.publish().toString();
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ cr.update(uri, values, null, null);
+ return uri.toString();
} catch (Exception e) {
Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
+ cr.delete(uri, null, null);
return null;
}
}
@@ -2318,6 +2287,14 @@ public final class MediaStore {
public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
/**
+ * Return the typical {@link Size} (in pixels) used internally when
+ * the given thumbnail kind is requested.
+ */
+ public static @NonNull Size getKindSize(int kind) {
+ return ThumbnailConstants.getKindSize(kind);
+ }
+
+ /**
* The blob raw data of thumbnail
*
* @deprecated this column never existed internally, and could never
@@ -3572,6 +3549,14 @@ public final class MediaStore {
public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
/**
+ * Return the typical {@link Size} (in pixels) used internally when
+ * the given thumbnail kind is requested.
+ */
+ public static @NonNull Size getKindSize(int kind) {
+ return ThumbnailConstants.getKindSize(kind);
+ }
+
+ /**
* The width of the thumbnal
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -3603,54 +3588,44 @@ public final class MediaStore {
*/
public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
final StorageManager sm = context.getSystemService(StorageManager.class);
- final Set<String> volumeNames = new ArraySet<>();
- for (VolumeInfo vi : sm.getVolumes()) {
- if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
- if (vi.isPrimary()) {
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
- } else {
- volumeNames.add(vi.getNormalizedFsUuid());
+ final Set<String> res = new ArraySet<>();
+ for (StorageVolume sv : sm.getStorageVolumes()) {
+ switch (sv.getState()) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY: {
+ final String volumeName = sv.getMediaStoreVolumeName();
+ if (volumeName != null) {
+ res.add(volumeName);
+ }
+ break;
}
}
}
- return volumeNames;
+ return res;
}
/**
- * Return list of all specific volume names that have recently been part of
+ * Return list of all recent volume names that have been part of
* {@link #VOLUME_EXTERNAL}.
* <p>
- * This includes both currently mounted volumes <em>and</em> recently
- * mounted (but currently unmounted) volumes. Any indexed metadata for these
- * volumes is preserved to optimize the speed of remounting at a later time.
- *
- * @hide
+ * These volume names are not currently mounted, but they're likely to
+ * reappear in the future, so apps are encouraged to preserve any indexed
+ * metadata related to these volumes to optimize user experiences.
+ * <p>
+ * Each specific volume name can be passed to APIs like
+ * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
+ * media on that storage device.
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) {
final StorageManager sm = context.getSystemService(StorageManager.class);
-
- // We always have primary storage
- final Set<String> volumeNames = new ArraySet<>();
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
-
- final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
- for (VolumeRecord rec : sm.getVolumeRecords()) {
- // Skip volumes without valid UUIDs
- if (TextUtils.isEmpty(rec.fsUuid)) continue;
-
- final VolumeInfo vi = sm.findVolumeByUuid(rec.fsUuid);
- if (vi != null && vi.isVisibleForUser(UserHandle.myUserId())
- && vi.isMountedReadable()) {
- // We're mounted right now
- volumeNames.add(rec.getNormalizedFsUuid());
- } else if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
- // We're not mounted right now, but we've been seen recently
- volumeNames.add(rec.getNormalizedFsUuid());
+ final Set<String> res = new ArraySet<>();
+ for (StorageVolume sv : sm.getRecentStorageVolumes()) {
+ final String volumeName = sv.getMediaStoreVolumeName();
+ if (volumeName != null) {
+ res.add(volumeName);
}
}
- return volumeNames;
+ return res;
}
/**
@@ -3703,97 +3678,6 @@ public final class MediaStore {
}
/**
- * Return path where the given specific volume is mounted. Not valid for
- * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
- * broad collections that cover many paths.
- *
- * @hide
- */
- @TestApi
- public static @NonNull File getVolumePath(@NonNull String volumeName)
- throws FileNotFoundException {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- return getVolumePath(sm.getVolumes(), volumeName);
- }
-
- /** {@hide} */
- public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes,
- @NonNull String volumeName) throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- switch (volumeName) {
- case VOLUME_INTERNAL:
- case VOLUME_EXTERNAL:
- throw new FileNotFoundException(volumeName + " has no associated path");
- }
-
- final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName);
- for (VolumeInfo volume : volumes) {
- final boolean matchPrimary = wantPrimary
- && volume.isPrimary();
- final boolean matchSecondary = !wantPrimary
- && Objects.equals(volume.getNormalizedFsUuid(), volumeName);
- if (matchPrimary || matchSecondary) {
- final File path = volume.getPathForUser(UserHandle.myUserId());
- if (path != null) {
- return path;
- }
- }
- }
- throw new FileNotFoundException("Failed to find path for " + volumeName);
- }
-
- /**
- * Return paths that should be scanned for the given volume.
- *
- * @hide
- */
- @TestApi
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
- public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
- throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- final Context context = AppGlobals.getInitialApplication();
- final UserManager um = context.getSystemService(UserManager.class);
-
- final ArrayList<File> res = new ArrayList<>();
- if (VOLUME_INTERNAL.equals(volumeName)) {
- addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
- for (String exactVolume : getExternalVolumeNames(context)) {
- addCanonicalFile(res, getVolumePath(exactVolume));
- }
- if (um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- } else {
- addCanonicalFile(res, getVolumePath(volumeName));
- if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- }
- return res;
- }
-
- private static void addCanonicalFile(List<File> list, File file) {
- try {
- list.add(file.getCanonicalFile());
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve " + file + ": " + e);
- list.add(file);
- }
- }
-
- /**
* Uri for querying the state of the media scanner.
*/
public static Uri getMediaScannerUri() {
@@ -3876,10 +3760,10 @@ public final class MediaStore {
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ in.putParcelable(EXTRA_URI, mediaUri);
+ in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
+ return out.getParcelable(EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -3906,134 +3790,43 @@ public final class MediaStore {
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ in.putParcelable(EXTRA_URI, documentUri);
+ in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
+ return out.getParcelable(EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
+ /** @hide */
+ @TestApi
+ public static void waitForIdle(@NonNull ContentResolver resolver) {
+ resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null);
+ }
+
/**
- * Calculate size of media contributed by given package under the calling
- * user. The meaning of "contributed" means it won't automatically be
- * deleted when the app is uninstalled.
+ * Perform a blocking scan of the given {@link File}, returning the
+ * {@link Uri} of the scanned file.
*
* @hide
*/
+ @SystemApi
@TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static @BytesLong long getContributedMediaSize(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
- return out.getLong(Intent.EXTRA_INDEX);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
+ @SuppressLint("StreamFiles")
+ public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) {
+ final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null);
+ return out.getParcelable(Intent.EXTRA_STREAM);
}
/**
- * Delete all media contributed by given package under the calling user. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
+ * Perform a blocking scan of the given storage volume.
*
* @hide
*/
+ @SystemApi
@TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static void deleteContributedMedia(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
- }
-
- /** @hide */
- @TestApi
- public static void waitForIdle(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- client.call(WAIT_FOR_IDLE_CALL, null, null);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /** @hide */
- public static void suicide(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver
- .acquireUnstableContentProviderClient(AUTHORITY)) {
- client.call(SUICIDE_CALL, null, null);
- } catch (Exception ignored) {
- }
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFile(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFileFromShell(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, true);
- }
-
- /** @hide */
- @TestApi
- public static void scanVolume(Context context, File file) {
- scan(context, SCAN_VOLUME_CALL, file, false);
- }
-
- /** @hide */
- public static Uri scanFile(ContentProviderClient client, File file) {
- return scan(client, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- private static Uri scan(Context context, String method, File file,
- boolean originatedFromShell) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- return scan(client, method, file, originatedFromShell);
- }
- }
-
- /** @hide */
- private static Uri scan(ContentProviderClient client, String method, File file,
- boolean originatedFromShell) {
- try {
- final Bundle in = new Bundle();
- in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
- in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
- final Bundle out = client.call(method, null, in);
- return out.getParcelable(Intent.EXTRA_STREAM);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
+ public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) {
+ resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 03181296885f..503d6db9df9c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -568,16 +568,47 @@ public final class Settings {
/**
* Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
* necessary.
+ * @deprecated See {@link #ACTION_BIOMETRIC_ENROLL}.
* <p>
* Input: Nothing.
* <p>
* Output: Nothing.
*/
+ @Deprecated
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_FINGERPRINT_ENROLL =
"android.settings.FINGERPRINT_ENROLL";
/**
+ * Activity Action: Show settings to enroll biometrics, and setup PIN/Pattern/Pass if
+ * necessary. By default, this prompts the user to enroll biometrics with strength
+ * Weak or above, as defined by the CDD. Only biometrics that meet or exceed Strong, as defined
+ * in the CDD are allowed to participate in Keystore operations.
+ * <p>
+ * Input: extras {@link #EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED} as an integer, with
+ * constants defined in {@link android.hardware.biometrics.BiometricManager.Authenticators},
+ * e.g. {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_STRONG}.
+ * If not specified, the default behavior is
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_WEAK}.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BIOMETRIC_ENROLL =
+ "android.settings.BIOMETRIC_ENROLL";
+
+ /**
+ * Activity Extra: The minimum strength to request enrollment for.
+ * <p>
+ * This can be passed as an extra field to the {@link #ACTION_BIOMETRIC_ENROLL} intent to
+ * indicate that only enrollment for sensors that meet this strength should be shown. The
+ * value should be one of the biometric strength constants defined in
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators}.
+ */
+ public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED =
+ "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED";
+
+ /**
* Activity Action: Show settings to allow configuration of cast endpoints.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -8392,6 +8423,20 @@ public final class Settings {
"navigation_mode";
/**
+ * Scale factor for the back gesture inset size on the left side of the screen.
+ * @hide
+ */
+ public static final String BACK_GESTURE_INSET_SCALE_LEFT =
+ "back_gesture_inset_scale_left";
+
+ /**
+ * Scale factor for the back gesture inset size on the right side of the screen.
+ * @hide
+ */
+ public static final String BACK_GESTURE_INSET_SCALE_RIGHT =
+ "back_gesture_inset_scale_right";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -13639,13 +13684,6 @@ public final class Settings {
public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
/**
- * Default user id to boot into. They map to user ids, for example, 10, 11, 12.
- *
- * @hide
- */
- public static final String DEFAULT_USER_ID_TO_BOOT_INTO = "default_boot_into_user_id";
-
- /**
* Persistent user id that is last logged in to.
*
* They map to user ids, for example, 10, 11, 12.
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index e53ebada55fb..72e9ad047ed7 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
+import android.view.inputmethod.InlineSuggestionsRequest;
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
@@ -116,20 +117,34 @@ public final class FillRequest implements Parcelable {
*/
private final @RequestFlags int mFlags;
+ /**
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+ *
+ * @return the suggestionspec
+ */
+ private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+
private void onConstructed() {
Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
}
- // Code below generated by codegen v1.0.0.
+ // Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
//
- // CHECKSTYLE:OFF Generated code
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
/** @hide */
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -184,6 +199,11 @@ public final class FillRequest implements Parcelable {
*
* @return any combination of {@link #FLAG_MANUAL_REQUEST} and
* {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+ * @param inlineSuggestionsRequest
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
* @hide
*/
@DataClass.Generated.Member
@@ -191,7 +211,8 @@ public final class FillRequest implements Parcelable {
int id,
@NonNull List<FillContext> fillContexts,
@Nullable Bundle clientState,
- @RequestFlags int flags) {
+ @RequestFlags int flags,
+ @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
this.mId = id;
this.mFillContexts = fillContexts;
com.android.internal.util.AnnotationValidations.validate(
@@ -203,6 +224,7 @@ public final class FillRequest implements Parcelable {
mFlags,
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST);
+ this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
}
@@ -256,6 +278,19 @@ public final class FillRequest implements Parcelable {
return mFlags;
}
+ /**
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+ *
+ * @return the suggestionspec
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
+ return mInlineSuggestionsRequest;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -266,29 +301,63 @@ public final class FillRequest implements Parcelable {
"id = " + mId + ", " +
"fillContexts = " + mFillContexts + ", " +
"clientState = " + mClientState + ", " +
- "flags = " + requestFlagsToString(mFlags) +
+ "flags = " + requestFlagsToString(mFlags) + ", " +
+ "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
" }";
}
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mClientState != null) flg |= 0x4;
+ if (mInlineSuggestionsRequest != null) flg |= 0x10;
dest.writeByte(flg);
dest.writeInt(mId);
dest.writeParcelableList(mFillContexts, flags);
if (mClientState != null) dest.writeBundle(mClientState);
dest.writeInt(mFlags);
+ if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ FillRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int id = in.readInt();
+ List<FillContext> fillContexts = new ArrayList<>();
+ in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+ Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
+ int flags = in.readInt();
+ InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+
+ this.mId = id;
+ this.mFillContexts = fillContexts;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFillContexts);
+ this.mClientState = clientState;
+ this.mFlags = flags;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST);
+ this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+ onConstructed();
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
= new Parcelable.Creator<FillRequest>() {
@@ -298,31 +367,21 @@ public final class FillRequest implements Parcelable {
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
- public FillRequest createFromParcel(Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int id = in.readInt();
- List<FillContext> fillContexts = new ArrayList<>();
- in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
- Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
- int flags = in.readInt();
- return new FillRequest(
- id,
- fillContexts,
- clientState,
- flags);
+ public FillRequest createFromParcel(@NonNull Parcel in) {
+ return new FillRequest(in);
}
};
@DataClass.Generated(
- time = 1565152134349L,
- codegenVersion = "1.0.0",
+ time = 1575928271155L,
+ codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index c99fe6149aab..02a6390a08bb 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
+import android.app.slice.Slice;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
@@ -86,6 +87,7 @@ public final class FillResponse implements Parcelable {
private int mRequestId;
private final @Nullable UserData mUserData;
private final @Nullable int[] mCancelIds;
+ private final @Nullable ParceledListSlice<Slice> mInlineSuggestionSlices;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -103,6 +105,8 @@ public final class FillResponse implements Parcelable {
mRequestId = INVALID_REQUEST_ID;
mUserData = builder.mUserData;
mCancelIds = builder.mCancelIds;
+ mInlineSuggestionSlices = (builder.mInlineSuggestionSlices != null)
+ ? new ParceledListSlice<>(builder.mInlineSuggestionSlices) : null;
}
/** @hide */
@@ -195,6 +199,11 @@ public final class FillResponse implements Parcelable {
return mCancelIds;
}
+ /** @hide */
+ public List<Slice> getInlineSuggestionSlices() {
+ return (mInlineSuggestionSlices != null) ? mInlineSuggestionSlices.getList() : null;
+ }
+
/**
* Builder for {@link FillResponse} objects. You must to provide at least
* one dataset or set an authentication intent with a presentation view.
@@ -215,6 +224,7 @@ public final class FillResponse implements Parcelable {
private boolean mDestroyed;
private UserData mUserData;
private int[] mCancelIds;
+ private ArrayList<Slice> mInlineSuggestionSlices;
/**
* Triggers a custom UI before before autofilling the screen with any data set in this
@@ -570,6 +580,20 @@ public final class FillResponse implements Parcelable {
}
/**
+ * TODO(b/137800469): add javadoc
+ */
+ @NonNull
+ public Builder addInlineSuggestionSlice(@NonNull Slice inlineSuggestionSlice) {
+ throwIfDestroyed();
+ throwIfAuthenticationCalled();
+ if (mInlineSuggestionSlices == null) {
+ mInlineSuggestionSlices = new ArrayList<>();
+ }
+ mInlineSuggestionSlices.add(inlineSuggestionSlice);
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
@@ -670,7 +694,9 @@ public final class FillResponse implements Parcelable {
if (mCancelIds != null) {
builder.append(", mCancelIds=").append(mCancelIds.length);
}
-
+ if (mInlineSuggestionSlices != null) {
+ builder.append(", inlinedSuggestions=").append(mInlineSuggestionSlices.getList());
+ }
return builder.append("]").toString();
}
@@ -699,7 +725,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelableArray(mFieldClassificationIds, flags);
parcel.writeInt(mFlags);
parcel.writeIntArray(mCancelIds);
-
+ parcel.writeParcelable(mInlineSuggestionSlices, flags);
parcel.writeInt(mRequestId);
}
@@ -755,6 +781,16 @@ public final class FillResponse implements Parcelable {
final int[] cancelIds = parcel.createIntArray();
builder.setCancelTargetIds(cancelIds);
+ final ParceledListSlice<Slice> parceledInlineSuggestionSlices =
+ parcel.readParcelable(null);
+ if (parceledInlineSuggestionSlices != null) {
+ final List<Slice> inlineSuggestionSlices = parceledInlineSuggestionSlices.getList();
+ final int size = inlineSuggestionSlices.size();
+ for (int i = 0; i < size; i++) {
+ builder.addInlineSuggestionSlice(inlineSuggestionSlices.get(i));
+ }
+ }
+
final FillResponse response = builder.build();
response.setRequestId(parcel.readInt());
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
new file mode 100644
index 000000000000..373e1e5f979f
--- /dev/null
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -0,0 +1,307 @@
+/*
+ * 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 android.service.dataloader;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.content.pm.InstallationFile;
+import android.content.pm.NamedParcelFileDescriptor;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * The base class for implementing data loader service to control data loaders. Expecting
+ * Incremental Service to bind to a children class of this.
+ *
+ * @hide
+ *
+ * Hide for now, should be @SystemApi
+ * TODO(b/136132412): update with latest API design
+ */
+public abstract class DataLoaderService extends Service {
+ private static final String TAG = "IncrementalDataLoaderService";
+ private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
+
+ public static final int DATA_LOADER_READY =
+ IDataLoaderStatusListener.DATA_LOADER_READY;
+ public static final int DATA_LOADER_NOT_READY =
+ IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
+ public static final int DATA_LOADER_RUNNING =
+ IDataLoaderStatusListener.DATA_LOADER_RUNNING;
+ public static final int DATA_LOADER_STOPPED =
+ IDataLoaderStatusListener.DATA_LOADER_STOPPED;
+ public static final int DATA_LOADER_SLOW_CONNECTION =
+ IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
+ public static final int DATA_LOADER_NO_CONNECTION =
+ IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
+ public static final int DATA_LOADER_CONNECTION_OK =
+ IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"DATA_LOADER_"}, value = {
+ DATA_LOADER_READY,
+ DATA_LOADER_NOT_READY,
+ DATA_LOADER_RUNNING,
+ DATA_LOADER_STOPPED,
+ DATA_LOADER_SLOW_CONNECTION,
+ DATA_LOADER_NO_CONNECTION,
+ DATA_LOADER_CONNECTION_OK
+ })
+ public @interface DataLoaderStatus {
+ }
+
+ /**
+ * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
+ * instance.
+ */
+ public abstract static class DataLoader {
+ /**
+ * A virtual constructor used to do simple initialization. Not ready to serve any data yet.
+ * All heavy-lifting has to be done in onStart.
+ *
+ * @param params Data loader configuration parameters.
+ * @param connector IncFS API wrapper.
+ * @param listener Used for reporting internal state to IncrementalService.
+ * @return True if initialization of a Data Loader was successful. False will be reported to
+ * IncrementalService and can cause an unmount of an IFS instance.
+ */
+ public abstract boolean onCreate(@NonNull DataLoaderParams params,
+ @NonNull FileSystemConnector connector,
+ @NonNull StatusListener listener);
+
+ /**
+ * Start the data loader. After this method returns data loader is considered to be ready to
+ * receive callbacks from IFS, supply data via connector and send status updates via
+ * callbacks.
+ *
+ * @return True if Data Loader was able to start. False will be reported to
+ * IncrementalService and can cause an unmount of an IFS instance.
+ */
+ public abstract boolean onStart();
+
+ /**
+ * Stop the data loader. Use to stop any additional threads and free up resources. Data
+ * loader is not longer responsible for supplying data. Start/Stop pair can be called
+ * multiple times e.g. if IFS detects corruption and data needs to be re-loaded.
+ */
+ public abstract void onStop();
+
+ /**
+ * Virtual destructor. Use to cleanup all internal state. After this method returns, the
+ * data loader can no longer use connector or callbacks. For any additional operations with
+ * this instance of IFS a new DataLoader will be created using createDataLoader method.
+ */
+ public abstract void onDestroy();
+ }
+
+ /**
+ * DataLoader factory method.
+ *
+ * @return An instance of a DataLoader.
+ */
+ public abstract @Nullable DataLoader onCreateDataLoader();
+
+ /**
+ * @hide
+ */
+ public final @NonNull IBinder onBind(@NonNull Intent intent) {
+ return (IBinder) mBinder;
+ }
+
+ private class DataLoaderBinderService extends IDataLoader.Stub {
+ private int mId;
+
+ @Override
+ public void create(int id, @NonNull Bundle options,
+ @NonNull IDataLoaderStatusListener listener)
+ throws IllegalArgumentException, RuntimeException {
+ mId = id;
+ final DataLoaderParamsParcel params = options.getParcelable("params");
+ if (params == null) {
+ throw new IllegalArgumentException("Must specify Incremental data loader params");
+ }
+ final FileSystemControlParcel control =
+ options.getParcelable("control");
+ if (control == null) {
+ throw new IllegalArgumentException("Must specify Incremental control parcel");
+ }
+ mStatusListener = listener;
+ try {
+ if (!nativeCreateDataLoader(id, control, params, listener)) {
+ Slog.e(TAG, "Failed to create native loader for " + mId);
+ }
+ } catch (Exception ex) {
+ destroy();
+ throw new RuntimeException(ex);
+ } finally {
+ // Closing FDs.
+ if (control.incremental.cmd != null) {
+ try {
+ control.incremental.cmd.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
+ }
+ }
+ if (control.incremental.log != null) {
+ try {
+ control.incremental.log.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
+ }
+ }
+ NamedParcelFileDescriptor[] fds = params.dynamicArgs;
+ for (NamedParcelFileDescriptor nfd : fds) {
+ try {
+ nfd.fd.close();
+ } catch (IOException e) {
+ Slog.e(TAG,
+ "Failed to close DynamicArgs parcel file descriptor " + e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void start(List<InstallationFile> fileInfos) {
+ if (!nativeStartDataLoader(mId)) {
+ Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (!nativeStopDataLoader(mId)) {
+ Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ if (!nativeDestroyDataLoader(mId)) {
+ Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
+ }
+ }
+ }
+
+ /**
+ *
+ * Used by the DataLoaderService implementations.
+ *
+ * @hide
+ *
+ * TODO(b/136132412) Should be @SystemApi
+ */
+ public static final class FileSystemConnector {
+ /**
+ * Creates a wrapper for an installation session connector.
+ * @hide
+ */
+ FileSystemConnector(IPackageInstallerSessionFileSystemConnector connector) {
+ mConnector = connector;
+ }
+
+ /**
+ * Write data to an installation file from an arbitrary FD.
+ *
+ * @param name name of file previously added to the installation session.
+ * @param offsetBytes offset into the file to begin writing at, or 0 to
+ * start at the beginning of the file.
+ * @param lengthBytes total size of the file being written, used to
+ * preallocate the underlying disk space, or -1 if unknown.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @param incomingFd FD to read bytes from.
+ * @throws IOException if trouble opening the file for writing, such as
+ * lack of disk space or unavailable media.
+ */
+ public void writeData(String name, long offsetBytes, long lengthBytes,
+ ParcelFileDescriptor incomingFd) throws IOException {
+ try {
+ mConnector.writeData(name, offsetBytes, lengthBytes, incomingFd);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private final IPackageInstallerSessionFileSystemConnector mConnector;
+ }
+
+ /**
+ * Wrapper for native reporting DataLoader statuses.
+ * @hide
+ * TODO(b/136132412) Should be @SystemApi
+ */
+ public static final class StatusListener {
+ /**
+ * Creates a wrapper for a native instance.
+ * @hide
+ */
+ StatusListener(long nativeInstance) {
+ mNativeInstance = nativeInstance;
+ }
+
+ /**
+ * Report the status of DataLoader. Used for system-wide notifications e.g., disabling
+ * applications which rely on this data loader to function properly.
+ *
+ * @param status status to report.
+ * @return True if status was reported successfully.
+ */
+ public boolean onStatusChanged(@DataLoaderStatus int status) {
+ return nativeReportStatus(mNativeInstance, status);
+ }
+
+ private final long mNativeInstance;
+ }
+
+ private IDataLoaderStatusListener mStatusListener = null;
+
+ /* Native methods */
+ private native boolean nativeCreateDataLoader(int storageId,
+ @NonNull FileSystemControlParcel control,
+ @NonNull DataLoaderParamsParcel params,
+ IDataLoaderStatusListener listener);
+
+ private native boolean nativeStartDataLoader(int storageId);
+
+ private native boolean nativeStopDataLoader(int storageId);
+
+ private native boolean nativeDestroyDataLoader(int storageId);
+
+ private static native boolean nativeReportStatus(long nativeInstance, int status);
+}
diff --git a/core/java/android/service/incremental/IncrementalDataLoaderService.java b/core/java/android/service/incremental/IncrementalDataLoaderService.java
deleted file mode 100644
index c4a06c8f53db..000000000000
--- a/core/java/android/service/incremental/IncrementalDataLoaderService.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * 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 android.service.incremental;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.IDataLoader;
-import android.content.pm.IDataLoaderStatusListener;
-import android.content.pm.InstallationFile;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.NamedParcelFileDescriptor;
-import android.util.Slog;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-import java.util.List;
-
-
-/**
- * The base class for implementing data loader service to control data loaders. Expecting
- * Incremental Service to bind to a children class of this.
- *
- * @hide
- *
- * Hide for now, should be @SystemApi
- * TODO(b/136132412): update with latest API design
- */
-public abstract class IncrementalDataLoaderService extends Service {
- private static final String TAG = "IncrementalDataLoaderService";
- private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
-
- public static final int DATA_LOADER_READY =
- IDataLoaderStatusListener.DATA_LOADER_READY;
- public static final int DATA_LOADER_NOT_READY =
- IDataLoaderStatusListener.DATA_LOADER_NOT_READY;
- public static final int DATA_LOADER_RUNNING =
- IDataLoaderStatusListener.DATA_LOADER_RUNNING;
- public static final int DATA_LOADER_STOPPED =
- IDataLoaderStatusListener.DATA_LOADER_STOPPED;
- public static final int DATA_LOADER_SLOW_CONNECTION =
- IDataLoaderStatusListener.DATA_LOADER_SLOW_CONNECTION;
- public static final int DATA_LOADER_NO_CONNECTION =
- IDataLoaderStatusListener.DATA_LOADER_NO_CONNECTION;
- public static final int DATA_LOADER_CONNECTION_OK =
- IDataLoaderStatusListener.DATA_LOADER_CONNECTION_OK;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"DATA_LOADER_"}, value = {
- DATA_LOADER_READY,
- DATA_LOADER_NOT_READY,
- DATA_LOADER_RUNNING,
- DATA_LOADER_STOPPED,
- DATA_LOADER_SLOW_CONNECTION,
- DATA_LOADER_NO_CONNECTION,
- DATA_LOADER_CONNECTION_OK
- })
- public @interface DataLoaderStatus {
- }
-
- /**
- * Incremental FileSystem block size.
- **/
- public static final int BLOCK_SIZE = 4096;
-
- /**
- * Data compression types
- */
- public static final int COMPRESSION_NONE = 0;
- public static final int COMPRESSION_LZ4 = 1;
-
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({COMPRESSION_NONE, COMPRESSION_LZ4})
- public @interface CompressionType {
- }
-
- /**
- * Managed DataLoader interface. Each instance corresponds to a single Incremental File System
- * instance.
- */
- public abstract static class DataLoader {
- /**
- * A virtual constructor used to do simple initialization. Not ready to serve any data yet.
- * All heavy-lifting has to be done in onStart.
- *
- * @param params Data loader configuration parameters.
- * @param connector IncFS API wrapper.
- * @param listener Used for reporting internal state to IncrementalService.
- * @return True if initialization of a Data Loader was successful. False will be reported to
- * IncrementalService and can cause an unmount of an IFS instance.
- */
- public abstract boolean onCreate(@NonNull IncrementalDataLoaderParams params,
- @NonNull FileSystemConnector connector,
- @NonNull StatusListener listener);
-
- /**
- * Start the data loader. After this method returns data loader is considered to be ready to
- * receive callbacks from IFS, supply data via connector and send status updates via
- * callbacks.
- *
- * @return True if Data Loader was able to start. False will be reported to
- * IncrementalService and can cause an unmount of an IFS instance.
- */
- public abstract boolean onStart();
-
- /**
- * Stop the data loader. Use to stop any additional threads and free up resources. Data
- * loader is not longer responsible for supplying data. Start/Stop pair can be called
- * multiple times e.g. if IFS detects corruption and data needs to be re-loaded.
- */
- public abstract void onStop();
-
- /**
- * Virtual destructor. Use to cleanup all internal state. After this method returns, the
- * data loader can no longer use connector or callbacks. For any additional operations with
- * this instance of IFS a new DataLoader will be created using createDataLoader method.
- */
- public abstract void onDestroy();
-
- /**
- * IFS reports a pending read each time the page needs to be loaded, e.g. missing.
- *
- * @param pendingReads array of blocks to load.
- *
- * TODO(b/136132412): avoid using collections
- */
- public abstract void onPendingReads(
- @NonNull Collection<FileSystemConnector.PendingReadInfo> pendingReads);
-
- /**
- * IFS tracks all reads and reports them using onPageReads.
- *
- * @param reads array of blocks.
- *
- * TODO(b/136132412): avoid using collections
- */
- public abstract void onPageReads(@NonNull Collection<FileSystemConnector.ReadInfo> reads);
-
- /**
- * IFS informs data loader that a new file has been created.
- * <p>
- * This can be used to prepare the data loader before it starts loading data. For example,
- * the data loader can keep a list of newly created files, so that it knows what files to
- * download from the server.
- *
- * @param inode The inode value of the new file.
- * @param metadata The metadata of the new file.
- */
- public abstract void onFileCreated(long inode, byte[] metadata);
- }
-
- /**
- * DataLoader factory method.
- *
- * @return An instance of a DataLoader.
- */
- public abstract @Nullable DataLoader onCreateDataLoader();
-
- /**
- * @hide
- */
- public final @NonNull IBinder onBind(@NonNull Intent intent) {
- return (IBinder) mBinder;
- }
-
- private class DataLoaderBinderService extends IDataLoader.Stub {
- private int mId;
-
- @Override
- public void create(int id, @NonNull Bundle options,
- @NonNull IDataLoaderStatusListener listener)
- throws IllegalArgumentException, RuntimeException {
- mId = id;
- final IncrementalDataLoaderParamsParcel params = options.getParcelable("params");
- if (params == null) {
- throw new IllegalArgumentException("Must specify Incremental data loader params");
- }
- final IncrementalFileSystemControlParcel control =
- options.getParcelable("control");
- if (control == null) {
- throw new IllegalArgumentException("Must specify Incremental control parcel");
- }
- mStatusListener = listener;
- try {
- if (!nativeCreateDataLoader(id, control, params, listener)) {
- Slog.e(TAG, "Failed to create native loader for " + mId);
- }
- } catch (Exception ex) {
- destroy();
- throw new RuntimeException(ex);
- } finally {
- // Closing FDs.
- if (control.cmd != null) {
- try {
- control.cmd.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
- }
- }
- if (control.log != null) {
- try {
- control.log.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
- }
- }
- NamedParcelFileDescriptor[] fds = params.dynamicArgs;
- for (NamedParcelFileDescriptor nfd : fds) {
- try {
- nfd.fd.close();
- } catch (IOException e) {
- Slog.e(TAG,
- "Failed to close DynamicArgs parcel file descriptor " + e);
- }
- }
- }
- }
-
- @Override
- public void start(List<InstallationFile> fileInfos) {
- if (!nativeStartDataLoader(mId)) {
- Slog.e(TAG, "Failed to start loader: loader not found for " + mId);
- }
- }
-
- @Override
- public void stop() {
- if (!nativeStopDataLoader(mId)) {
- Slog.w(TAG, "Failed to stop loader: loader not found for " + mId);
- }
- }
-
- @Override
- public void destroy() {
- if (!nativeDestroyDataLoader(mId)) {
- Slog.w(TAG, "Failed to destroy loader: loader not found for " + mId);
- }
- }
-
- @Override
- // TODO(b/136132412): remove this
- public void onFileCreated(long inode, byte[] metadata) {
- if (!nativeOnFileCreated(mId, inode, metadata)) {
- Slog.w(TAG, "Failed to handle onFileCreated for storage:" + mId
- + " inode:" + inode);
- }
- }
- }
-
- /**
- * IncFs API wrapper for writing pages and getting page missing info. Non-hidden methods are
- * expected to be called by the IncrementalDataLoaderService implemented by developers.
- *
- * @hide
- *
- * TODO(b/136132412) Should be @SystemApi
- */
- public static final class FileSystemConnector {
- /**
- * Defines a block address. A block is the unit of data chunk that IncFs operates with.
- *
- * @hide
- */
- public static class BlockAddress {
- /**
- * Linux inode uniquely identifies file within a single IFS instance.
- */
- private final long mFileIno;
- /**
- * Index of a 4K block within a file.
- */
- private final int mBlockIndex;
-
- public BlockAddress(long fileIno, int blockIndex) {
- this.mFileIno = fileIno;
- this.mBlockIndex = blockIndex;
- }
-
- public long getFileIno() {
- return mFileIno;
- }
-
- public int getBlockIndex() {
- return mBlockIndex;
- }
- }
-
- /**
- * A block is the unit of data chunk that IncFs operates with.
- *
- * @hide
- */
- public static class Block extends BlockAddress {
- /**
- * Data content of the block.
- */
- private final @NonNull byte[] mDataBytes;
-
- public Block(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
- super(fileIno, blockIndex);
- this.mDataBytes = dataBytes;
- }
- }
-
- /**
- * Defines a page/block inside a file.
- */
- public static class DataBlock extends Block {
- /**
- * Compression type of the data block.
- */
- private final @CompressionType int mCompressionType;
-
- public DataBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes,
- @CompressionType int compressionType) {
- super(fileIno, blockIndex, dataBytes);
- this.mCompressionType = compressionType;
- }
- }
-
- /**
- * Defines a hash block for a certain file. A hash block index is the index in an array of
- * hashes which is the 1-d representation of the hash tree. One DataBlock might be
- * associated with multiple HashBlocks.
- */
- public static class HashBlock extends Block {
- public HashBlock(long fileIno, int blockIndex, @NonNull byte[] dataBytes) {
- super(fileIno, blockIndex, dataBytes);
- }
- }
-
- /**
- * Information about a page that is pending to be read.
- */
- public static class PendingReadInfo extends BlockAddress {
- PendingReadInfo(long fileIno, int blockIndex) {
- super(fileIno, blockIndex);
- }
- }
-
- /**
- * Information about a page that is read.
- */
- public static class ReadInfo extends BlockAddress {
- /**
- * A monotonically increasing read timestamp.
- */
- private final long mTimePoint;
- /**
- * Number of blocks read starting from blockIndex.
- */
- private final int mBlockCount;
-
- ReadInfo(long timePoint, long fileIno, int firstBlockIndex, int blockCount) {
- super(fileIno, firstBlockIndex);
- this.mTimePoint = timePoint;
- this.mBlockCount = blockCount;
- }
-
- public long getTimePoint() {
- return mTimePoint;
- }
-
- public int getBlockCount() {
- return mBlockCount;
- }
- }
-
- /**
- * Defines the dynamic information about an IncFs file.
- */
- public static class FileInfo {
- /**
- * BitSet to show if any block is available at each block index.
- */
- private final @NonNull
- byte[] mBlockBitmap;
-
- /**
- * @hide
- */
- public FileInfo(@NonNull byte[] blockBitmap) {
- this.mBlockBitmap = blockBitmap;
- }
- }
-
- /**
- * Creates a wrapper for a native instance.
- */
- FileSystemConnector(long nativeInstance) {
- mNativeInstance = nativeInstance;
- }
-
- /**
- * Checks whether a range in a file if loaded.
- *
- * @param node inode of the file.
- * @param start The starting offset of the range.
- * @param end The ending offset of the range.
- * @return True if the file is fully loaded.
- */
- public boolean isFileRangeLoaded(long node, long start, long end) {
- return nativeIsFileRangeLoadedNode(mNativeInstance, node, start, end);
- }
-
- /**
- * Gets the metadata of a file.
- *
- * @param node inode of the file.
- * @return The metadata object.
- */
- @NonNull
- public byte[] getFileMetadata(long node) throws IOException {
- final byte[] metadata = nativeGetFileMetadataNode(mNativeInstance, node);
- if (metadata == null || metadata.length == 0) {
- throw new IOException(
- "IncrementalFileSystem failed to obtain metadata for node: " + node);
- }
- return metadata;
- }
-
- /**
- * Gets the dynamic information of a file, such as page bitmaps. Can be used to get missing
- * page indices by the FileSystemConnector.
- *
- * @param node inode of the file.
- * @return Dynamic file info.
- */
- @NonNull
- public FileInfo getDynamicFileInfo(long node) throws IOException {
- final byte[] blockBitmap = nativeGetFileInfoNode(mNativeInstance, node);
- if (blockBitmap == null || blockBitmap.length == 0) {
- throw new IOException(
- "IncrementalFileSystem failed to obtain dynamic file info for node: "
- + node);
- }
- return new FileInfo(blockBitmap);
- }
-
- /**
- * Writes a page's data and/or hashes.
- *
- * @param dataBlocks the DataBlock objects that contain data block index and data bytes.
- * @param hashBlocks the HashBlock objects that contain hash indices and hash bytes.
- *
- * TODO(b/136132412): change API to avoid dynamic allocation of data block objects
- */
- public void writeMissingData(@NonNull DataBlock[] dataBlocks,
- @Nullable HashBlock[] hashBlocks) throws IOException {
- if (!nativeWriteMissingData(mNativeInstance, dataBlocks, hashBlocks)) {
- throw new IOException("IncrementalFileSystem failed to write missing data.");
- }
- }
-
- /**
- * Writes the signer block of a file. Expecting the connector to call this when it got
- * signing data from data loader.
- *
- * @param node the file to be written to.
- * @param signerData the raw signer data byte array.
- */
- public void writeSignerData(long node, @NonNull byte[] signerData)
- throws IOException {
- if (!nativeWriteSignerDataNode(mNativeInstance, node, signerData)) {
- throw new IOException(
- "IncrementalFileSystem failed to write signer data of node " + node);
- }
- }
-
- private final long mNativeInstance;
- }
-
- /**
- * Wrapper for native reporting DataLoader statuses.
- *
- * @hide
- *
- * TODO(b/136132412) Should be @SystemApi
- */
- public static final class StatusListener {
- /**
- * Creates a wrapper for a native instance.
- *
- * @hide
- */
- StatusListener(long nativeInstance) {
- mNativeInstance = nativeInstance;
- }
-
- /**
- * Report the status of DataLoader. Used for system-wide notifications e.g., disabling
- * applications which rely on this data loader to function properly.
- *
- * @param status status to report.
- * @return True if status was reported successfully.
- */
- public boolean onStatusChanged(@DataLoaderStatus int status) {
- return nativeReportStatus(mNativeInstance, status);
- }
-
- private final long mNativeInstance;
- }
-
- private IDataLoaderStatusListener mStatusListener = null;
-
- /* Native methods */
- private native boolean nativeCreateDataLoader(int storageId,
- @NonNull IncrementalFileSystemControlParcel control,
- @NonNull IncrementalDataLoaderParamsParcel params,
- IDataLoaderStatusListener listener);
-
- private native boolean nativeStartDataLoader(int storageId);
-
- private native boolean nativeStopDataLoader(int storageId);
-
- private native boolean nativeDestroyDataLoader(int storageId);
-
- private static native boolean nativeOnFileCreated(int storageId,
- long inode, byte[] metadata);
-
- private static native boolean nativeIsFileRangeLoadedNode(
- long nativeInstance, long node, long start, long end);
-
- private static native boolean nativeWriteMissingData(
- long nativeInstance, FileSystemConnector.DataBlock[] dataBlocks,
- FileSystemConnector.HashBlock[] hashBlocks);
-
- private static native boolean nativeWriteSignerDataNode(
- long nativeInstance, long node, byte[] signerData);
-
- private static native byte[] nativeGetFileMetadataNode(
- long nativeInstance, long node);
-
- private static native byte[] nativeGetFileInfoNode(
- long nativeInstance, long node);
-
- private static native boolean nativeReportStatus(long nativeInstance, int status);
-}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 8ab687f0d001..c84fbc7287bb 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -124,6 +124,13 @@ public final class Adjustment implements Parcelable {
public static final String KEY_IMPORTANCE = "key_importance";
/**
+ * Data type: float, a ranking score from 0 (lowest) to 1 (highest).
+ * Used to rank notifications inside that fall under the same classification (i.e. alerting,
+ * silenced).
+ */
+ public static final String KEY_RANKING_SCORE = "key_ranking_score";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 716a5225ea91..51a9c864ee29 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -35,8 +35,8 @@ import android.telephony.Annotation.SrvccState;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IPhoneStateListener;
import dalvik.system.VMRuntime;
@@ -169,14 +169,6 @@ public class PhoneStateListener {
public static final int LISTEN_SIGNAL_STRENGTHS = 0x00000100;
/**
- * Listen for changes to OTASP mode.
- *
- * @see #onOtaspChanged
- * @hide
- */
- public static final int LISTEN_OTASP_CHANGED = 0x00000200;
-
- /**
* Listen for changes to observed cell info.
*
* @see #onCellInfoChanged
@@ -196,12 +188,13 @@ public class PhoneStateListener {
/**
* Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
*
- * @see #onPreciseDataConnectionStateChanged
+ * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * or the calling app has carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @hide
+ * @see #onPreciseDataConnectionStateChanged
*/
- @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 0x00001000;
/**
@@ -624,29 +617,6 @@ public class PhoneStateListener {
// default implementation empty
}
-
- /**
- * The Over The Air Service Provisioning (OTASP) has changed on the registered subscription.
- * Note, the registration subId comes from {@link TelephonyManager} object which registers
- * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subId. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * Requires the READ_PHONE_STATE permission.
- * @param otaspMode is integer <code>OTASP_UNKNOWN=1<code>
- * means the value is currently unknown and the system should wait until
- * <code>OTASP_NEEDED=2<code> or <code>OTASP_NOT_NEEDED=3<code> is received before
- * making the decision to perform OTASP or not.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public void onOtaspChanged(int otaspMode) {
- // default implementation empty
- }
-
/**
* Callback invoked when a observed cell info has changed or new cells have been added
* or removed on the registered subscription.
@@ -719,8 +689,9 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when data connection state changes with precise information
- * on the registered subscription.
+ * Callback providing update about the default/internet data connection on the registered
+ * subscription.
+ *
* Note, the registration subId comes from {@link TelephonyManager} object which registers
* PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
* If this TelephonyManager object was created with
@@ -728,12 +699,13 @@ public class PhoneStateListener {
* subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param dataConnectionState {@link PreciseDataConnectionState}
+ * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * or the calling app has carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @hide
+ * @param dataConnectionState {@link PreciseDataConnectionState}
*/
- @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public void onPreciseDataConnectionStateChanged(
@NonNull PreciseDataConnectionState dataConnectionState) {
// default implementation empty
@@ -1042,11 +1014,21 @@ public class PhoneStateListener {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> {
- psl.onDataConnectionStateChanged(state, networkType);
- psl.onDataConnectionStateChanged(state);
- }));
+ if (state == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> {
+ psl.onDataConnectionStateChanged(
+ TelephonyManager.DATA_CONNECTED, networkType);
+ psl.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED);
+ }));
+ } else {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> {
+ psl.onDataConnectionStateChanged(state, networkType);
+ psl.onDataConnectionStateChanged(state);
+ }));
+ }
}
public void onDataActivity(int direction) {
@@ -1065,14 +1047,6 @@ public class PhoneStateListener {
() -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
}
- public void onOtaspChanged(int otaspMode) {
- PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
- if (psl == null) return;
-
- Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> psl.onOtaspChanged(otaspMode)));
- }
-
public void onCellInfoChanged(List<CellInfo> cellInfo) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9d7b57ba250d..f574160e5303 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -21,17 +21,13 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DataFailureCause;
-import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.RadioPowerState;
@@ -357,27 +353,18 @@ public class TelephonyRegistryManager {
* @param subId for which data connection state changed.
* @param slotIndex for which data connections state changed. Can be derived from subId except
* when subId is invalid.
- * @param state latest data connection state, e.g,
- * @param isDataConnectivityPossible indicates if data is allowed
- * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
- * @param linkProperties {@link LinkProperties} associated with this data connection.
- * @param networkCapabilities {@link NetworkCapabilities} associated with this data connection.
- * @param networkType associated with this data connection.
- * @param roaming {@code true} indicates in roaming, {@false} otherwise.
- * @see TelephonyManager#DATA_DISCONNECTED
- * @see TelephonyManager#isDataConnectivityPossible()
+ * @param apnType the APN type that triggered this update
+ * @param preciseState the PreciseDataConnectionState
*
+ * @see android.telephony.PreciseDataConnection
+ * @see TelephonyManager#DATA_DISCONNECTED
* @hide
*/
- public void notifyDataConnectionForSubscriber(int slotIndex, int subId, @DataState int state,
- boolean isDataConnectivityPossible,
- @ApnType String apn, String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+ public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
+ String apnType, PreciseDataConnectionState preciseState) {
try {
- sRegistry.notifyDataConnectionForSubscriber(slotIndex, subId, state,
- isDataConnectivityPossible,
- apn, apnType, linkProperties, networkCapabilities, networkType, roaming);
+ sRegistry.notifyDataConnectionForSubscriber(
+ slotIndex, subId, apnType, preciseState);
} catch (RemoteException ex) {
// system process is dead
}
@@ -600,22 +587,6 @@ public class TelephonyRegistryManager {
}
/**
- * Notify over the air sim provisioning(OTASP) mode changed on certain subscription.
- *
- * @param subId for which otasp mode changed.
- * @param otaspMode latest mode for OTASP e.g, OTASP needed.
- *
- * @hide
- */
- public void notifyOtaspChanged(int subId, int otaspMode) {
- try {
- sRegistry.notifyOtaspChanged(subId, otaspMode);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- /**
* Notify precise call state changed on certain subscription, including foreground, background
* and ringcall states.
*
@@ -662,25 +633,6 @@ public class TelephonyRegistryManager {
}
/**
- * Notify data connection failed on certain subscription.
- *
- * @param subId for which data connection failed.
- * @param slotIndex for which data conenction faled. Can be derived from subId except when subId
- * is invalid.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. Note each data
- * connection can support multiple anyTypes.
- *
- * @hide
- */
- public void notifyDataConnectionFailed(int subId, int slotIndex, String apnType) {
- try {
- sRegistry.notifyDataConnectionFailedForSubscriber(slotIndex, subId, apnType);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- /**
* TODO change from bundle to CellLocation?
* @hide
*/
diff --git a/core/java/android/util/CloseGuard.java b/core/java/android/util/CloseGuard.java
index c39a6c9aac93..6ac769623bff 100644
--- a/core/java/android/util/CloseGuard.java
+++ b/core/java/android/util/CloseGuard.java
@@ -38,6 +38,11 @@ import android.annotation.NonNull;
* public void cleanup() {
* guard.close();
* ...;
+ * if (Build.VERSION.SDK_INT >= 28) {
+ * Reference.reachabilityFence(this);
+ * }
+ * // For full correctness in the absence of a close() call, other methods may also need
+ * // reachabilityFence() calls.
* }
*
* protected void finalize() throws Throwable {
@@ -75,7 +80,9 @@ import android.annotation.NonNull;
* public void cleanup() {
* guard.close();
* ...;
- * Reference.reachabilityFence(this);
+ * if (Build.VERSION.SDK_INT >= 28) {
+ * Reference.reachabilityFence(this);
+ * }
* // For full correctness in the absence of a close() call, other methods may also need
* // reachabilityFence() calls.
* }
diff --git a/core/java/android/view/FrameMetricsObserver.java b/core/java/android/view/FrameMetricsObserver.java
index 0f38e847f4bd..41bc9a742752 100644
--- a/core/java/android/view/FrameMetricsObserver.java
+++ b/core/java/android/view/FrameMetricsObserver.java
@@ -17,11 +17,8 @@
package android.view;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Looper;
-import android.os.MessageQueue;
-
-import com.android.internal.util.VirtualRefBasePtr;
+import android.graphics.HardwareRendererObserver;
+import android.os.Handler;
import java.lang.ref.WeakReference;
@@ -31,47 +28,39 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-public class FrameMetricsObserver {
- @UnsupportedAppUsage
- private MessageQueue mMessageQueue;
-
- private WeakReference<Window> mWindow;
-
- @UnsupportedAppUsage
- private FrameMetrics mFrameMetrics;
-
- /* pacage */ Window.OnFrameMetricsAvailableListener mListener;
- /** @hide */
- public VirtualRefBasePtr mNative;
+public class FrameMetricsObserver
+ implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+ private final WeakReference<Window> mWindow;
+ private final FrameMetrics mFrameMetrics;
+ private final HardwareRendererObserver mObserver;
+ /*package*/ final Window.OnFrameMetricsAvailableListener mListener;
/**
* Creates a FrameMetricsObserver
*
- * @param looper the looper to use when invoking callbacks
+ * @param handler the Handler to use when invoking callbacks
*/
- FrameMetricsObserver(@NonNull Window window, @NonNull Looper looper,
+ FrameMetricsObserver(@NonNull Window window, @NonNull Handler handler,
@NonNull Window.OnFrameMetricsAvailableListener listener) {
- if (looper == null) {
- throw new NullPointerException("looper cannot be null");
- }
-
- mMessageQueue = looper.getQueue();
- if (mMessageQueue == null) {
- throw new IllegalStateException("invalid looper, null message queue\n");
- }
-
- mFrameMetrics = new FrameMetrics();
mWindow = new WeakReference<>(window);
mListener = listener;
+ mFrameMetrics = new FrameMetrics();
+ mObserver = new HardwareRendererObserver(this, mFrameMetrics.mTimingData, handler);
}
- // Called by native on the provided Handler
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private void notifyDataAvailable(int dropCount) {
- final Window window = mWindow.get();
- if (window != null) {
- mListener.onFrameMetricsAvailable(window, mFrameMetrics, dropCount);
+ /**
+ * Implementation of OnFrameMetricsAvailableListener
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * @Override
+ */
+ public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+ if (mWindow.get() != null) {
+ mListener.onFrameMetricsAvailable(mWindow.get(), mFrameMetrics,
+ dropCountSinceLastInvocation);
}
}
+
+ /*package*/ HardwareRendererObserver getRendererObserver() {
+ return mObserver;
+ }
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7ea4f3062e21..cdfd397a732a 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -68,22 +68,26 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
private final InsetsController mController;
private final WindowInsetsAnimationCallback.InsetsAnimation mAnimation;
private final Rect mFrame;
+ private final boolean mFade;
private Insets mCurrentInsets;
private Insets mPendingInsets;
private float mPendingFraction;
private boolean mFinished;
private boolean mCancelled;
private boolean mShownOnFinish;
+ private float mCurrentAlpha;
+ private float mPendingAlpha;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
- InsetsController controller, long durationMs) {
+ InsetsController controller, long durationMs, boolean fade) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
+ mFade = fade;
mTransactionApplierSupplier = transactionApplierSupplier;
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
@@ -100,6 +104,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
InsetsController.INTERPOLATOR, durationMs);
+ mAnimation.setAlpha(getCurrentAlpha());
mController.dispatchAnimationStarted(mAnimation,
new AnimationBounds(mHiddenInsets, mShownInsets));
}
@@ -120,6 +125,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
}
@Override
+ public float getCurrentAlpha() {
+ return mCurrentAlpha;
+ }
+
+ @Override
@InsetsType public int getTypes() {
return mTypes;
}
@@ -136,6 +146,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
}
mPendingFraction = sanitize(fraction);
mPendingInsets = sanitize(insets);
+ mPendingAlpha = 1 - sanitize(alpha);
mController.scheduleApplyChangeInsets();
}
@@ -148,17 +159,24 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
return false;
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
+ final Float alphaOffset = 1 - mPendingAlpha;
ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, state);
- updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, state);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, state);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, state);
- updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, params, state);
+ updateLeashesForSide(ISIDE_LEFT, offset.left, mShownInsets.left, mPendingInsets.left,
+ params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_TOP, offset.top, mShownInsets.top, mPendingInsets.top, params,
+ state, alphaOffset);
+ updateLeashesForSide(ISIDE_RIGHT, offset.right, mShownInsets.right, mPendingInsets.right,
+ params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom,
+ mPendingInsets.bottom, params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, 0 /* maxInset */,
+ params, state, alphaOffset);
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
mAnimation.setFraction(mPendingFraction);
+ mCurrentAlpha = 1 - alphaOffset;
if (mFinished) {
mController.notifyFinished(this, mShownOnFinish);
}
@@ -233,7 +251,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
}
private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
- ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
+ int maxInset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
if (items == null) {
return;
@@ -257,7 +275,9 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
// If the system is controlling the insets source, the leash can be null.
if (leash != null) {
- surfaceParams.add(new SurfaceParams(leash, 1f /* alpha */, mTmpMatrix,
+ // TODO: use a better interpolation for fade.
+ alpha = mFade ? ((float) maxInset / inset * 0.3f + 0.7f) : alpha;
+ surfaceParams.add(new SurfaceParams(leash, alpha, mTmpMatrix,
null /* windowCrop */, 0 /* layer */, 0f /* cornerRadius*/,
side == ISIDE_FLOATING ? consumer.isVisible() : inset != 0 /* visible */));
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 88703112ae80..5563d629f25e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -361,12 +361,12 @@ public class InsetsController implements WindowInsetsController {
listener.onCancelled();
return;
}
- controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs);
+ controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
}
private void controlAnimationUnchecked(@InsetsType int types,
WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
- long durationMs) {
+ long durationMs, boolean fade) {
if (types == 0) {
// nothing to animate.
return;
@@ -397,7 +397,7 @@ public class InsetsController implements WindowInsetsController {
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
frame, mState, listener, typesReady,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs);
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs, fade);
mAnimationControls.add(controller);
}
@@ -588,7 +588,8 @@ public class InsetsController implements WindowInsetsController {
// Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct.
controlAnimationUnchecked(
- types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs());
+ types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
+ true /* fade */);
}
private void hideDirectly(@InsetsType int types) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2d0b954deced..1639dbe697d3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7147,10 +7147,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mFrameMetricsObservers = new ArrayList<>();
}
- FrameMetricsObserver fmo = new FrameMetricsObserver(window,
- handler.getLooper(), listener);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
mFrameMetricsObservers.add(fmo);
- mAttachInfo.mThreadedRenderer.addFrameMetricsObserver(fmo);
+ mAttachInfo.mThreadedRenderer.addObserver(fmo.getRendererObserver());
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
}
@@ -7159,8 +7158,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mFrameMetricsObservers = new ArrayList<>();
}
- FrameMetricsObserver fmo = new FrameMetricsObserver(window,
- handler.getLooper(), listener);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
mFrameMetricsObservers.add(fmo);
}
}
@@ -7182,7 +7180,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mFrameMetricsObservers != null) {
mFrameMetricsObservers.remove(fmo);
if (renderer != null) {
- renderer.removeFrameMetricsObserver(fmo);
+ renderer.removeObserver(fmo.getRendererObserver());
}
}
}
@@ -7192,7 +7190,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ThreadedRenderer renderer = getThreadedRenderer();
if (renderer != null) {
for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
- renderer.addFrameMetricsObserver(fmo);
+ renderer.addObserver(fmo.getRendererObserver());
}
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
@@ -16185,7 +16183,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
- * {@link #getHeight()} to see how wide a view is after layout.
+ * {@link #getHeight()} to see how high a view is after layout.
*
* @return The measured height of this view as a bit mask.
*/
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 8ae85201be92..5e71f271f1d4 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -89,6 +89,7 @@ public interface WindowInsetsAnimationCallback {
private float mFraction;
@Nullable private final Interpolator mInterpolator;
private long mDurationMs;
+ private float mAlpha;
public InsetsAnimation(
@InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
@@ -177,6 +178,18 @@ public interface WindowInsetsAnimationCallback {
public void setDuration(long durationMs) {
mDurationMs = durationMs;
}
+
+ /**
+ * @return alpha of {@link WindowInsets.Type.InsetsType}.
+ */
+ @FloatRange(from = 0f, to = 1f)
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
+ mAlpha = alpha;
+ }
}
/**
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 51491037c934..2bf0d277268d 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -93,6 +93,12 @@ public interface WindowInsetsAnimationController {
float getCurrentFraction();
/**
+ * Current alpha value of the window.
+ * @return float value between 0 and 1.
+ */
+ float getCurrentAlpha();
+
+ /**
* @return The {@link InsetsType}s this object is currently controlling.
*/
@InsetsType int getTypes();
diff --git a/core/java/android/view/inline/InlineContentView.java b/core/java/android/view/inline/InlineContentView.java
index 4bc2176e8279..b143fad778ec 100644
--- a/core/java/android/view/inline/InlineContentView.java
+++ b/core/java/android/view/inline/InlineContentView.java
@@ -50,7 +50,10 @@ public class InlineContentView extends SurfaceView {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
- // TODO(b/137800469): implement this.
+ new SurfaceControl.Transaction()
+ .setVisibility(surfaceControl, false)
+ .reparent(surfaceControl, null)
+ .apply();
}
});
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 25e34d36aa9a..c10144e6ee25 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -271,7 +271,7 @@ public final class InlineSuggestion implements Parcelable {
};
@DataClass.Generated(
- time = 1574446220398L,
+ time = 1575933636929L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 112653aa34e3..1e5a3b027609 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -21,11 +21,16 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.util.Log;
+import android.view.autofill.AutofillId;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
/**
* The InputMethod interface represents an input method which can generate key
@@ -104,6 +109,20 @@ public interface InputMethod {
}
/**
+ * Called to notify the IME that Autofill Frameworks requested an inline suggestions request.
+ *
+ * @hide
+ */
+ default void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("InputMethod", "RemoteException calling onInlineSuggestionsUnsupported: " + e);
+ }
+ }
+
+ /**
* Called first thing after an input method is created, this supplies a
* unique token for the session it has with the system service. It is
* needed to identify itself with the service to validate its operations.
diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java
index 6070b5341cf9..e633404fd7d8 100644
--- a/core/java/android/view/textclassifier/ConversationAction.java
+++ b/core/java/android/view/textclassifier/ConversationAction.java
@@ -26,9 +26,8 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
+import java.util.Objects;
/** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
public final class ConversationAction implements Parcelable {
@@ -133,11 +132,11 @@ public final class ConversationAction implements Parcelable {
@Nullable CharSequence textReply,
float score,
@NonNull Bundle extras) {
- mType = Preconditions.checkNotNull(type);
+ mType = Objects.requireNonNull(type);
mAction = action;
mTextReply = textReply;
mScore = score;
- mExtras = Preconditions.checkNotNull(extras);
+ mExtras = Objects.requireNonNull(extras);
}
private ConversationAction(Parcel in) {
@@ -221,7 +220,7 @@ public final class ConversationAction implements Parcelable {
private Bundle mExtras;
public Builder(@NonNull @ActionType String actionType) {
- mType = Preconditions.checkNotNull(actionType);
+ mType = Objects.requireNonNull(actionType);
}
/**
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index f7f503ab60ec..80027b1ed75d 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -38,6 +38,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Represents a list of actions suggested by a {@link TextClassifier} on a given conversation.
@@ -66,7 +67,7 @@ public final class ConversationActions implements Parcelable {
public ConversationActions(
@NonNull List<ConversationAction> conversationActions, @Nullable String id) {
mConversationActions =
- Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions));
+ Collections.unmodifiableList(Objects.requireNonNull(conversationActions));
mId = id;
}
@@ -149,7 +150,7 @@ public final class ConversationActions implements Parcelable {
mAuthor = author;
mReferenceTime = referenceTime;
mText = text;
- mExtras = Preconditions.checkNotNull(bundle);
+ mExtras = Objects.requireNonNull(bundle);
}
private Message(Parcel in) {
@@ -243,7 +244,7 @@ public final class ConversationActions implements Parcelable {
* {@link #PERSON_USER_OTHERS} to represent a remote user.
*/
public Builder(@NonNull Person author) {
- mAuthor = Preconditions.checkNotNull(author);
+ mAuthor = Objects.requireNonNull(author);
}
/** Sets the text of this message. */
@@ -329,8 +330,8 @@ public final class ConversationActions implements Parcelable {
int maxSuggestions,
@Nullable @Hint List<String> hints,
@NonNull Bundle extras) {
- mConversation = Preconditions.checkNotNull(conversation);
- mTypeConfig = Preconditions.checkNotNull(typeConfig);
+ mConversation = Objects.requireNonNull(conversation);
+ mTypeConfig = Objects.requireNonNull(typeConfig);
mMaxSuggestions = maxSuggestions;
mHints = hints;
mExtras = extras;
@@ -483,7 +484,7 @@ public final class ConversationActions implements Parcelable {
* actions for.
*/
public Builder(@NonNull List<Message> conversation) {
- mConversation = Preconditions.checkNotNull(conversation);
+ mConversation = Objects.requireNonNull(conversation);
}
/**
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
index 3c51c38952f0..4c12dda0109a 100644
--- a/core/java/android/view/textclassifier/EntityConfidence.java
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -22,12 +22,11 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
-import com.android.internal.util.Preconditions;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Helper object for setting and getting entity scores for classified text.
@@ -42,7 +41,7 @@ final class EntityConfidence implements Parcelable {
EntityConfidence() {}
EntityConfidence(@NonNull EntityConfidence source) {
- Preconditions.checkNotNull(source);
+ Objects.requireNonNull(source);
mEntityConfidence.putAll(source.mEntityConfidence);
mSortedEntities.addAll(source.mSortedEntities);
}
@@ -56,7 +55,7 @@ final class EntityConfidence implements Parcelable {
* 1 (high confidence).
*/
EntityConfidence(@NonNull Map<String, Float> source) {
- Preconditions.checkNotNull(source);
+ Objects.requireNonNull(source);
// Prune non-existent entities and clamp to 1.
mEntityConfidence.ensureCapacity(source.size());
diff --git a/core/java/android/view/textclassifier/GenerateLinksLogger.java b/core/java/android/view/textclassifier/GenerateLinksLogger.java
index 3996b277aa1c..17ec73ad395f 100644
--- a/core/java/android/view/textclassifier/GenerateLinksLogger.java
+++ b/core/java/android/view/textclassifier/GenerateLinksLogger.java
@@ -23,7 +23,6 @@ import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
import java.util.Locale;
import java.util.Map;
@@ -66,9 +65,9 @@ public final class GenerateLinksLogger {
/** Logs statistics about a call to generateLinks. */
public void logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName,
long latencyMs) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(links);
- Preconditions.checkNotNull(callingPackageName);
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(links);
+ Objects.requireNonNull(callingPackageName);
if (!shouldLog()) {
return;
}
diff --git a/core/java/android/view/textclassifier/ModelFileManager.java b/core/java/android/view/textclassifier/ModelFileManager.java
index e04285db75be..0a4ff5d559ab 100644
--- a/core/java/android/view/textclassifier/ModelFileManager.java
+++ b/core/java/android/view/textclassifier/ModelFileManager.java
@@ -23,7 +23,6 @@ import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
@@ -51,7 +50,7 @@ public final class ModelFileManager {
private List<ModelFile> mModelFiles;
public ModelFileManager(Supplier<List<ModelFile>> modelFileSupplier) {
- mModelFileSupplier = Preconditions.checkNotNull(modelFileSupplier);
+ mModelFileSupplier = Objects.requireNonNull(modelFileSupplier);
}
/**
@@ -106,12 +105,12 @@ public final class ModelFileManager {
File updatedModelFile,
Function<Integer, Integer> versionSupplier,
Function<Integer, String> supportedLocalesSupplier) {
- mUpdatedModelFile = Preconditions.checkNotNull(updatedModelFile);
- mFactoryModelDir = Preconditions.checkNotNull(factoryModelDir);
+ mUpdatedModelFile = Objects.requireNonNull(updatedModelFile);
+ mFactoryModelDir = Objects.requireNonNull(factoryModelDir);
mModelFilenamePattern = Pattern.compile(
- Preconditions.checkNotNull(factoryModelFileNameRegex));
- mVersionSupplier = Preconditions.checkNotNull(versionSupplier);
- mSupportedLocalesSupplier = Preconditions.checkNotNull(supportedLocalesSupplier);
+ Objects.requireNonNull(factoryModelFileNameRegex));
+ mVersionSupplier = Objects.requireNonNull(versionSupplier);
+ mSupportedLocalesSupplier = Objects.requireNonNull(supportedLocalesSupplier);
}
@Override
@@ -208,10 +207,10 @@ public final class ModelFileManager {
public ModelFile(File file, int version, List<Locale> supportedLocales,
String supportedLocalesStr,
boolean languageIndependent) {
- mFile = Preconditions.checkNotNull(file);
+ mFile = Objects.requireNonNull(file);
mVersion = version;
- mSupportedLocales = Preconditions.checkNotNull(supportedLocales);
- mSupportedLocalesStr = Preconditions.checkNotNull(supportedLocalesStr);
+ mSupportedLocales = Objects.requireNonNull(supportedLocales);
+ mSupportedLocalesStr = Objects.requireNonNull(supportedLocalesStr);
mLanguageIndependent = languageIndependent;
}
@@ -232,7 +231,7 @@ public final class ModelFileManager {
/** Returns whether the language supports any language in the given ranges. */
public boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) {
- Preconditions.checkNotNull(languageRanges);
+ Objects.requireNonNull(languageRanges);
return mLanguageIndependent || Locale.lookup(languageRanges, mSupportedLocales) != null;
}
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 7d1077e50258..09cb7a07faa8 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -149,7 +149,7 @@ public final class SelectionEvent implements Parcelable {
mAbsoluteStart = start;
mAbsoluteEnd = end;
mEventType = eventType;
- mEntityType = Preconditions.checkNotNull(entityType);
+ mEntityType = Objects.requireNonNull(entityType);
mResultId = resultId;
mInvocationMethod = invocationMethod;
}
@@ -257,7 +257,7 @@ public final class SelectionEvent implements Parcelable {
public static SelectionEvent createSelectionModifiedEvent(
int start, int end, @NonNull TextClassification classification) {
Preconditions.checkArgument(end >= start, "end cannot be less than start");
- Preconditions.checkNotNull(classification);
+ Objects.requireNonNull(classification);
final String entityType = classification.getEntityCount() > 0
? classification.getEntity(0)
: TextClassifier.TYPE_UNKNOWN;
@@ -281,7 +281,7 @@ public final class SelectionEvent implements Parcelable {
public static SelectionEvent createSelectionModifiedEvent(
int start, int end, @NonNull TextSelection selection) {
Preconditions.checkArgument(end >= start, "end cannot be less than start");
- Preconditions.checkNotNull(selection);
+ Objects.requireNonNull(selection);
final String entityType = selection.getEntityCount() > 0
? selection.getEntity(0)
: TextClassifier.TYPE_UNKNOWN;
@@ -329,7 +329,7 @@ public final class SelectionEvent implements Parcelable {
int start, int end, @SelectionEvent.ActionType int actionType,
@NonNull TextClassification classification) {
Preconditions.checkArgument(end >= start, "end cannot be less than start");
- Preconditions.checkNotNull(classification);
+ Objects.requireNonNull(classification);
checkActionType(actionType);
final String entityType = classification.getEntityCount() > 0
? classification.getEntity(0)
@@ -398,7 +398,7 @@ public final class SelectionEvent implements Parcelable {
}
void setEntityType(@EntityType String entityType) {
- mEntityType = Preconditions.checkNotNull(entityType);
+ mEntityType = Objects.requireNonNull(entityType);
}
/**
diff --git a/core/java/android/view/textclassifier/SelectionSessionLogger.java b/core/java/android/view/textclassifier/SelectionSessionLogger.java
index 20cc94433872..9c9b2d0f1ce2 100644
--- a/core/java/android/view/textclassifier/SelectionSessionLogger.java
+++ b/core/java/android/view/textclassifier/SelectionSessionLogger.java
@@ -24,7 +24,6 @@ import android.metrics.LogMaker;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
import java.text.BreakIterator;
import java.util.List;
@@ -65,12 +64,12 @@ public final class SelectionSessionLogger {
@VisibleForTesting
public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) {
- mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
+ mMetricsLogger = Objects.requireNonNull(metricsLogger);
}
/** Emits a selection event to the logs. */
public void writeEvent(@NonNull SelectionEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
.setType(getLogType(event))
.setSubtype(getLogSubType(event))
@@ -240,7 +239,7 @@ public final class SelectionSessionLogger {
* Returns a token iterator for tokenizing text for logging purposes.
*/
public static BreakIterator getTokenIterator(@NonNull Locale locale) {
- return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
+ return BreakIterator.getWordInstance(Objects.requireNonNull(locale));
}
/**
@@ -249,9 +248,9 @@ public final class SelectionSessionLogger {
public static String createId(
String text, int start, int end, Context context, int modelVersion,
List<Locale> locales) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(context);
- Preconditions.checkNotNull(locales);
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(locales);
final StringJoiner localesJoiner = new StringJoiner(",");
for (Locale locale : locales) {
localesJoiner.add(locale.toLanguageTag());
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 66f75043a9c8..138d25dd7c83 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -33,8 +33,8 @@ import android.service.textclassifier.TextClassifierService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -61,10 +61,10 @@ public final class SystemTextClassifier implements TextClassifier {
throws ServiceManager.ServiceNotFoundException {
mManagerService = ITextClassifierService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
- mSettings = Preconditions.checkNotNull(settings);
+ mSettings = Objects.requireNonNull(settings);
mFallback = context.getSystemService(TextClassificationManager.class)
.getTextClassifier(TextClassifier.LOCAL);
- mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
+ mPackageName = Objects.requireNonNull(context.getOpPackageName());
mUserId = context.getUserId();
}
@@ -74,7 +74,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
@WorkerThread
public TextSelection suggestSelection(TextSelection.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
@@ -98,7 +98,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
@WorkerThread
public TextClassification classifyText(TextClassification.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
@@ -122,7 +122,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
@WorkerThread
public TextLinks generateLinks(@NonNull TextLinks.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
@@ -147,7 +147,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
public void onSelectionEvent(SelectionEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
Utils.checkMainThread();
try {
@@ -160,7 +160,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
Utils.checkMainThread();
try {
@@ -178,7 +178,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
public TextLanguage detectLanguage(TextLanguage.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
@@ -199,7 +199,7 @@ public final class SystemTextClassifier implements TextClassifier {
@Override
public ConversationActions suggestConversationActions(ConversationActions.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
@@ -260,7 +260,7 @@ public final class SystemTextClassifier implements TextClassifier {
void initializeRemoteSession(
@NonNull TextClassificationContext classificationContext,
@NonNull TextClassificationSessionId sessionId) {
- mSessionId = Preconditions.checkNotNull(sessionId);
+ mSessionId = Objects.requireNonNull(sessionId);
try {
classificationContext.setUserId(mUserId);
mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 93f7103c3d8d..3628d2d40c1e 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -150,7 +150,7 @@ public final class TextClassification implements Parcelable {
mLegacyIntent = legacyIntent;
mLegacyOnClickListener = legacyOnClickListener;
mActions = Collections.unmodifiableList(actions);
- mEntityConfidence = Preconditions.checkNotNull(entityConfidence);
+ mEntityConfidence = Objects.requireNonNull(entityConfidence);
mId = id;
mExtras = extras;
}
@@ -287,7 +287,7 @@ public final class TextClassification implements Parcelable {
* @hide
*/
public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
- Preconditions.checkNotNull(intent);
+ Objects.requireNonNull(intent);
return v -> {
try {
intent.send();
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index e4baaac9a300..930765b29197 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -24,9 +24,8 @@ import android.os.Parcelable;
import android.os.UserHandle;
import android.view.textclassifier.TextClassifier.WidgetType;
-import com.android.internal.util.Preconditions;
-
import java.util.Locale;
+import java.util.Objects;
/**
* A representation of the context in which text classification would be performed.
@@ -44,8 +43,8 @@ public final class TextClassificationContext implements Parcelable {
String packageName,
String widgetType,
String widgetVersion) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mWidgetType = Preconditions.checkNotNull(widgetType);
+ mPackageName = Objects.requireNonNull(packageName);
+ mWidgetType = Objects.requireNonNull(widgetType);
mWidgetVersion = widgetVersion;
}
@@ -121,8 +120,8 @@ public final class TextClassificationContext implements Parcelable {
* @return this builder
*/
public Builder(@NonNull String packageName, @NonNull @WidgetType String widgetType) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mWidgetType = Preconditions.checkNotNull(widgetType);
+ mPackageName = Objects.requireNonNull(packageName);
+ mWidgetType = Objects.requireNonNull(widgetType);
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index 7c25bf0f277d..978ac5b2493e 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -31,9 +31,9 @@ import android.view.textclassifier.TextClassifier.TextClassifierType;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
+import java.util.Objects;
import java.util.Set;
/**
@@ -71,7 +71,7 @@ public final class TextClassificationManager {
/** @hide */
public TextClassificationManager(Context context) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mSessionFactory = mDefaultSessionFactory;
mSettingsObserver = new SettingsObserver(this);
}
@@ -157,10 +157,10 @@ public final class TextClassificationManager {
@NonNull
public TextClassifier createTextClassificationSession(
@NonNull TextClassificationContext classificationContext) {
- Preconditions.checkNotNull(classificationContext);
+ Objects.requireNonNull(classificationContext);
final TextClassifier textClassifier =
mSessionFactory.createTextClassificationSession(classificationContext);
- Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
+ Objects.requireNonNull(textClassifier, "Session Factory should never return null");
return textClassifier;
}
@@ -170,8 +170,8 @@ public final class TextClassificationManager {
*/
public TextClassifier createTextClassificationSession(
TextClassificationContext classificationContext, TextClassifier textClassifier) {
- Preconditions.checkNotNull(classificationContext);
- Preconditions.checkNotNull(textClassifier);
+ Objects.requireNonNull(classificationContext);
+ Objects.requireNonNull(textClassifier);
return new TextClassificationSession(classificationContext, textClassifier);
}
@@ -280,7 +280,7 @@ public final class TextClassificationManager {
/** @hide */
public static TextClassificationConstants getSettings(Context context) {
- Preconditions.checkNotNull(context);
+ Objects.requireNonNull(context);
final TextClassificationManager tcm =
context.getSystemService(TextClassificationManager.class);
if (tcm != null) {
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index 6a706f5183ce..abfbc6c4f4ae 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -20,6 +20,7 @@ import android.annotation.WorkerThread;
import android.view.textclassifier.SelectionEvent.InvocationMethod;
import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* Session-aware TextClassifier.
@@ -37,8 +38,8 @@ final class TextClassificationSession implements TextClassifier {
private boolean mDestroyed;
TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
- mClassificationContext = Preconditions.checkNotNull(context);
- mDelegate = Preconditions.checkNotNull(delegate);
+ mClassificationContext = Objects.requireNonNull(context);
+ mDelegate = Objects.requireNonNull(delegate);
mSessionId = new TextClassificationSessionId();
mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
initializeRemoteSession();
@@ -149,8 +150,8 @@ final class TextClassificationSession implements TextClassifier {
SelectionEventHelper(
TextClassificationSessionId sessionId, TextClassificationContext context) {
- mSessionId = Preconditions.checkNotNull(sessionId);
- mContext = Preconditions.checkNotNull(context);
+ mSessionId = Objects.requireNonNull(sessionId);
+ mContext = Objects.requireNonNull(context);
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java
index 8b68e17aa681..f90e6b2e407c 100644
--- a/core/java/android/view/textclassifier/TextClassificationSessionId.java
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -20,9 +20,8 @@ import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.util.Locale;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -118,7 +117,7 @@ public final class TextClassificationSessionId implements Parcelable {
@Override
public TextClassificationSessionId createFromParcel(Parcel parcel) {
return new TextClassificationSessionId(
- Preconditions.checkNotNull(parcel.readString()));
+ Objects.requireNonNull(parcel.readString()));
}
@Override
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ac8a429a0fd6..9b3369390b6a 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -46,6 +46,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -193,7 +194,7 @@ public interface TextClassifier {
@WorkerThread
@NonNull
default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
}
@@ -252,7 +253,7 @@ public interface TextClassifier {
@WorkerThread
@NonNull
default TextClassification classifyText(@NonNull TextClassification.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return TextClassification.EMPTY;
}
@@ -313,7 +314,7 @@ public interface TextClassifier {
@WorkerThread
@NonNull
default TextLinks generateLinks(@NonNull TextLinks.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return new TextLinks.Builder(request.getText().toString()).build();
}
@@ -346,7 +347,7 @@ public interface TextClassifier {
@WorkerThread
@NonNull
default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return TextLanguage.EMPTY;
}
@@ -358,7 +359,7 @@ public interface TextClassifier {
@NonNull
default ConversationActions suggestConversationActions(
@NonNull ConversationActions.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return new ConversationActions(Collections.emptyList(), null);
}
@@ -427,9 +428,9 @@ public interface TextClassifier {
List<String> excludedEntityTypes,
List<String> hints,
boolean includeTypesFromTextClassifier) {
- mIncludedTypes = Preconditions.checkNotNull(includedEntityTypes);
- mExcludedTypes = Preconditions.checkNotNull(excludedEntityTypes);
- mHints = Preconditions.checkNotNull(hints);
+ mIncludedTypes = Objects.requireNonNull(includedEntityTypes);
+ mExcludedTypes = Objects.requireNonNull(excludedEntityTypes);
+ mHints = Objects.requireNonNull(hints);
mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index eb6aec07aad9..db8801150f0d 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -25,11 +25,11 @@ import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
/**
* This class represents events that are sent by components to the {@link TextClassifier} to report
@@ -549,7 +549,7 @@ public abstract class TextClassifierEvent implements Parcelable {
*/
@NonNull
public T setEntityTypes(@NonNull String... entityTypes) {
- Preconditions.checkNotNull(entityTypes);
+ Objects.requireNonNull(entityTypes);
mEntityTypes = new String[entityTypes.length];
System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
return self();
@@ -587,7 +587,7 @@ public abstract class TextClassifierEvent implements Parcelable {
*/
@NonNull
public T setScores(@NonNull float... scores) {
- Preconditions.checkNotNull(scores);
+ Objects.requireNonNull(scores);
mScores = new float[scores.length];
System.arraycopy(scores, 0, mScores, 0, scores.length);
return self();
@@ -652,7 +652,7 @@ public abstract class TextClassifierEvent implements Parcelable {
*/
@NonNull
public T setExtras(@NonNull Bundle extras) {
- mExtras = Preconditions.checkNotNull(extras);
+ mExtras = Objects.requireNonNull(extras);
return self();
}
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
index 3e088b8565f2..8162699a74c6 100644
--- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
+++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
@@ -29,7 +29,7 @@ import android.metrics.LogMaker;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
@@ -50,12 +50,12 @@ public final class TextClassifierEventTronLogger {
@VisibleForTesting
public TextClassifierEventTronLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
+ mMetricsLogger = Objects.requireNonNull(metricsLogger);
}
/** Emits a text classifier event to the logs. */
public void writeEvent(TextClassifierEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
int category = getCategory(event);
if (category == -1) {
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 024c379e0165..61bd7c72b80b 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -130,9 +130,9 @@ public final class TextClassifierImpl implements TextClassifier {
public TextClassifierImpl(
Context context, TextClassificationConstants settings, TextClassifier fallback) {
- mContext = Preconditions.checkNotNull(context);
- mFallback = Preconditions.checkNotNull(fallback);
- mSettings = Preconditions.checkNotNull(settings);
+ mContext = Objects.requireNonNull(context);
+ mFallback = Objects.requireNonNull(fallback);
+ mSettings = Objects.requireNonNull(settings);
mGenerateLinksLogger = new GenerateLinksLogger(mSettings.getGenerateLinksLogSampleRate());
mAnnotatorModelFileManager = new ModelFileManager(
new ModelFileManager.ModelFileSupplierImpl(
@@ -180,7 +180,7 @@ public final class TextClassifierImpl implements TextClassifier {
@Override
@WorkerThread
public TextSelection suggestSelection(TextSelection.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
final int rangeLength = request.getEndIndex() - request.getStartIndex();
@@ -245,7 +245,7 @@ public final class TextClassifierImpl implements TextClassifier {
@Override
@WorkerThread
public TextClassification classifyText(TextClassification.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
final int rangeLength = request.getEndIndex() - request.getStartIndex();
@@ -285,7 +285,7 @@ public final class TextClassifierImpl implements TextClassifier {
@Override
@WorkerThread
public TextLinks generateLinks(@NonNull TextLinks.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength());
Utils.checkMainThread();
@@ -399,7 +399,7 @@ public final class TextClassifierImpl implements TextClassifier {
/** @inheritDoc */
@Override
public TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
final TextLanguage.Builder builder = new TextLanguage.Builder();
@@ -420,7 +420,7 @@ public final class TextClassifierImpl implements TextClassifier {
@Override
public ConversationActions suggestConversationActions(ConversationActions.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
ActionsSuggestionsModel actionsImpl = getActionsImpl();
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index bbb7f07e0fdc..237d1a9e7838 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -48,6 +48,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Function;
/**
@@ -155,7 +156,7 @@ public final class TextLinks implements Parcelable {
@NonNull Spannable text,
@ApplyStrategy int applyStrategy,
@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
- Preconditions.checkNotNull(text);
+ Objects.requireNonNull(text);
return new TextLinksParams.Builder()
.setApplyStrategy(applyStrategy)
.setSpanFactory(spanFactory)
@@ -223,10 +224,10 @@ public final class TextLinks implements Parcelable {
*/
private TextLink(int start, int end, @NonNull EntityConfidence entityConfidence,
@NonNull Bundle extras, @Nullable URLSpan urlSpan) {
- Preconditions.checkNotNull(entityConfidence);
+ Objects.requireNonNull(entityConfidence);
Preconditions.checkArgument(!entityConfidence.getEntities().isEmpty());
Preconditions.checkArgument(start <= end);
- Preconditions.checkNotNull(extras);
+ Objects.requireNonNull(extras);
mStart = start;
mEnd = end;
mEntityScores = entityConfidence;
@@ -455,7 +456,7 @@ public final class TextLinks implements Parcelable {
@Nullable private Bundle mExtras;
public Builder(@NonNull CharSequence text) {
- mText = Preconditions.checkNotNull(text);
+ mText = Objects.requireNonNull(text);
}
/**
@@ -654,7 +655,7 @@ public final class TextLinks implements Parcelable {
* @param fullText The full text to annotate with links
*/
public Builder(@NonNull String fullText) {
- mFullText = Preconditions.checkNotNull(fullText);
+ mFullText = Objects.requireNonNull(fullText);
mLinks = new ArrayList<>();
}
diff --git a/core/java/android/view/textclassifier/TextLinksParams.java b/core/java/android/view/textclassifier/TextLinksParams.java
index 8af423300406..b7d63bfabd6d 100644
--- a/core/java/android/view/textclassifier/TextLinksParams.java
+++ b/core/java/android/view/textclassifier/TextLinksParams.java
@@ -25,10 +25,9 @@ import android.text.util.Linkify.LinkifyMask;
import android.view.textclassifier.TextLinks.TextLink;
import android.view.textclassifier.TextLinks.TextLinkSpan;
-import com.android.internal.util.Preconditions;
-
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Function;
/**
@@ -103,8 +102,8 @@ public final class TextLinksParams {
*/
@TextLinks.Status
public int apply(@NonNull Spannable text, @NonNull TextLinks textLinks) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(textLinks);
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(textLinks);
final String textString = text.toString();
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 0c8974956a7a..4a36cbfc35e5 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -36,6 +36,7 @@ import com.android.internal.util.Preconditions;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
/**
* Information about where text selection should be.
@@ -165,7 +166,7 @@ public final class TextSelection implements Parcelable {
public Builder setEntityType(
@NonNull @EntityType String type,
@FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
- Preconditions.checkNotNull(type);
+ Objects.requireNonNull(type);
mEntityConfidence.put(type, confidenceScore);
return this;
}
diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java
index 30fc20ea86a1..cbd9d1a522f6 100644
--- a/core/java/android/view/textclassifier/intent/LabeledIntent.java
+++ b/core/java/android/view/textclassifier/intent/LabeledIntent.java
@@ -32,7 +32,8 @@ import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassifier;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
/**
* Helper class to store the information from which RemoteActions are built.
@@ -86,9 +87,9 @@ public final class LabeledIntent {
}
this.titleWithoutEntity = titleWithoutEntity;
this.titleWithEntity = titleWithEntity;
- this.description = Preconditions.checkNotNull(description);
+ this.description = Objects.requireNonNull(description);
this.descriptionWithAppName = descriptionWithAppName;
- this.intent = Preconditions.checkNotNull(intent);
+ this.intent = Objects.requireNonNull(intent);
this.requestCode = requestCode;
}
@@ -198,8 +199,8 @@ public final class LabeledIntent {
public final RemoteAction remoteAction;
public Result(Intent resolvedIntent, RemoteAction remoteAction) {
- this.resolvedIntent = Preconditions.checkNotNull(resolvedIntent);
- this.remoteAction = Preconditions.checkNotNull(remoteAction);
+ this.resolvedIntent = Objects.requireNonNull(resolvedIntent);
+ this.remoteAction = Objects.requireNonNull(remoteAction);
}
}
diff --git a/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java
index 111fc6ab5f9f..aef4bd6c7351 100644
--- a/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java
+++ b/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java
@@ -30,6 +30,7 @@ import com.google.android.textclassifier.RemoteActionTemplate;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Creates intents based on {@link RemoteActionTemplate} objects for a ClassificationResult.
@@ -44,8 +45,8 @@ public final class TemplateClassificationIntentFactory implements Classification
public TemplateClassificationIntentFactory(TemplateIntentFactory templateIntentFactory,
ClassificationIntentFactory fallback) {
- mTemplateIntentFactory = Preconditions.checkNotNull(templateIntentFactory);
- mFallback = Preconditions.checkNotNull(fallback);
+ mTemplateIntentFactory = Objects.requireNonNull(templateIntentFactory);
+ mFallback = Objects.requireNonNull(fallback);
}
/**
diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 157c43597a44..d510724fd8d3 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -105,14 +105,14 @@ public final class SmartSelectionEventTracker {
public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
mWidgetType = widgetType;
mWidgetVersion = null;
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
}
public SmartSelectionEventTracker(
@NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) {
mWidgetType = widgetType;
mWidgetVersion = widgetVersion;
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
}
/**
@@ -122,7 +122,7 @@ public final class SmartSelectionEventTracker {
*/
@UnsupportedAppUsage(trackingBug = 136637107)
public void logEvent(@NonNull SelectionEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null
&& DEBUG_LOG_ENABLED) {
@@ -435,8 +435,8 @@ public final class SmartSelectionEventTracker {
mStart = start;
mEnd = end;
mEventType = eventType;
- mEntityType = Preconditions.checkNotNull(entityType);
- mVersionTag = Preconditions.checkNotNull(versionTag);
+ mEntityType = Objects.requireNonNull(entityType);
+ mVersionTag = Objects.requireNonNull(versionTag);
}
/**
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index d44fbd7c6f8e..3e26f6369747 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,6 +18,7 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -38,19 +39,19 @@ import android.util.AttributeSet;
* guide.</p>
*
* <p><strong>XML attributes</strong></p>
- * <p>
- * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
- * {@link android.R.styleable#Button Button Attributes},
- * {@link android.R.styleable#TextView TextView Attributes},
+ * <p>
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
+ * {@link android.R.styleable#Button Button Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
* {@link android.R.styleable#View View Attributes}
* </p>
*/
public class RadioButton extends CompoundButton {
-
+
public RadioButton(Context context) {
this(context, null);
}
-
+
public RadioButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
}
@@ -81,4 +82,20 @@ public class RadioButton extends CompoundButton {
public CharSequence getAccessibilityClassName() {
return RadioButton.class.getName();
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (getParent() instanceof RadioGroup) {
+ RadioGroup radioGroup = (RadioGroup) getParent();
+ if (radioGroup.getOrientation() == LinearLayout.HORIZONTAL) {
+ info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1,
+ radioGroup.getIndexWithinVisibleButtons(this), 1, false, isChecked()));
+ } else {
+ info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
+ radioGroup.getIndexWithinVisibleButtons(this), 1, 0, 1,
+ false, isChecked()));
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index c62c16c7e719..90bc0a3ee7b2 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -18,6 +18,7 @@ package android.widget;
import android.annotation.IdRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,6 +27,7 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStructure;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -93,6 +95,7 @@ public class RadioGroup extends LinearLayout {
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
// retrieve selected radio button as requested by the user in the
// XML layout file
@@ -475,4 +478,50 @@ public class RadioGroup extends LinearLayout {
}
return null;
}
-}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (this.getOrientation() == HORIZONTAL) {
+ info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1,
+ getVisibleChildCount(), false,
+ AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+ } else {
+ info.setCollectionInfo(
+ AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(),
+ 1, false,
+ AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+ }
+ }
+
+ private int getVisibleChildCount() {
+ int count = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (this.getChildAt(i) instanceof RadioButton) {
+ if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ int getIndexWithinVisibleButtons(@Nullable View child) {
+ if (!(child instanceof RadioButton)) {
+ return -1;
+ }
+ int index = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (this.getChildAt(i) instanceof RadioButton) {
+ RadioButton radioButton = (RadioButton) this.getChildAt(i);
+ if (radioButton == child) {
+ return index;
+ }
+ if (radioButton.getVisibility() == VISIBLE) {
+ index++;
+ }
+ }
+ }
+ return -1;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index ac2336c4a10f..fbd29ba2f592 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -309,6 +309,9 @@ public class Switch extends CompoundButton {
// Refresh display with current params
refreshDrawableState();
+ // Default state is derived from on/off-text, so state has to be updated when on/off-text
+ // are updated.
+ setDefaultStateDescritption();
setChecked(isChecked());
}
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index d47405b3b11e..59e0c16cde37 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -58,6 +58,9 @@ public class ToggleButton extends CompoundButton {
mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff);
mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f);
syncTextState();
+ // Default state is derived from on/off-text, so state has to be updated when on/off-text
+ // are updated.
+ setDefaultStateDescritption();
a.recycle();
}
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index c64b7051276a..08022e983892 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -14,6 +14,7 @@
* limitations under the License.
*/
package com.android.internal.app;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
@@ -23,10 +24,10 @@ import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.PagerAdapter;
-
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.ViewPager;
+import java.util.Objects;
+
/**
* Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
* intent resolution (including share sheet).
@@ -42,7 +43,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
private int mCurrentPage;
AbstractMultiProfilePagerAdapter(Context context, int currentPage) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage;
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f361784e81d7..48853bfe116a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -859,14 +859,21 @@ public class ChooserActivity extends ResolverActivity implements
return new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
- // TODO(arangelov): Dispatch this to all adapters when we have the helper methods
- // in a follow-up CL
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- updateProfileViewButton();
+ handlePackagesChanged();
}
};
}
+ /**
+ * Update UI to reflect changes in data.
+ */
+ public void handlePackagesChanged() {
+ // TODO(arangelov): Dispatch this to all adapters when we have the helper methods
+ // in a follow-up CL
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+ updateProfileViewButton();
+ }
+
private void onCopyButtonClicked(View v) {
Intent targetIntent = getTargetIntent();
if (targetIntent == null) {
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
index bdbe2109cf05..21efc78bddf8 100644
--- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -116,7 +116,7 @@ public class ResolverTargetActionsDialogFragment extends DialogFragment
pinComponent(mTargetInfos.get(which).getResolvedComponentName());
}
// Force the chooser to requery and resort things
- getActivity().recreate();
+ ((ChooserActivity) getActivity()).handlePackagesChanged();
} else {
// Last item in dialog is App Info
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index f5708a5c89af..dec9ae701fb2 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -259,7 +259,7 @@ public abstract class FileSystemProvider extends DocumentsProvider {
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
}
- MediaStore.scanFile(getContext(), file);
+ MediaStore.scanFile(getContext().getContentResolver(), file);
return childId;
}
@@ -316,10 +316,10 @@ public abstract class FileSystemProvider extends DocumentsProvider {
private void moveInMediaStore(@Nullable File oldVisibleFile, @Nullable File newVisibleFile) {
if (oldVisibleFile != null) {
- MediaStore.scanFile(getContext(), oldVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), oldVisibleFile);
}
if (newVisibleFile != null) {
- MediaStore.scanFile(getContext(), newVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), newVisibleFile);
}
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 6e9c4c36d40b..6b76a0f4127d 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -28,9 +28,9 @@ import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
import java.util.HashSet;
+import java.util.Objects;
/**
* Helper class for monitoring the state of packages: adding, removing,
@@ -93,7 +93,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
throw new IllegalStateException("Already registered");
}
mRegisteredContext = context;
- mRegisteredHandler = Preconditions.checkNotNull(handler);
+ mRegisteredHandler = Objects.requireNonNull(handler);
if (user != null) {
context.registerReceiverAsUser(this, user, sPackageFilt, null, mRegisteredHandler);
context.registerReceiverAsUser(this, user, sNonDataFilt, null, mRegisteredHandler);
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index 98d679eb776b..857377afd3e8 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -32,7 +32,6 @@ import android.text.TextUtils;
import android.util.DebugUtils;
import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.PrintWriter;
@@ -40,6 +39,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -352,7 +352,7 @@ public interface ServiceConnector<I extends IInterface> {
@Override
public <R> CompletionAwareJob<I, R> postForResult(@NonNull Job<I, R> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
- task.mDelegate = Preconditions.checkNotNull(job);
+ task.mDelegate = Objects.requireNonNull(job);
enqueue(task);
return task;
}
@@ -360,7 +360,7 @@ public interface ServiceConnector<I extends IInterface> {
@Override
public <R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
- task.mDelegate = Preconditions.checkNotNull((Job) job);
+ task.mDelegate = Objects.requireNonNull((Job) job);
task.mAsync = true;
enqueue(task);
return task;
diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java
index 9d653bad4d00..b1d85f78984c 100644
--- a/core/java/com/android/internal/infra/WhitelistHelper.java
+++ b/core/java/com/android/internal/infra/WhitelistHelper.java
@@ -23,10 +23,9 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import com.android.internal.util.Preconditions;
-
import java.io.PrintWriter;
import java.util.List;
+import java.util.Objects;
/**
* Helper class for keeping track of whitelisted packages/activities.
@@ -107,7 +106,7 @@ public final class WhitelistHelper {
* Returns {@code true} if the entire package is whitelisted.
*/
public boolean isWhitelisted(@NonNull String packageName) {
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
if (mWhitelistedPackages == null) return false;
@@ -119,7 +118,7 @@ public final class WhitelistHelper {
* Returns {@code true} if the specified activity is whitelisted.
*/
public boolean isWhitelisted(@NonNull ComponentName componentName) {
- Preconditions.checkNotNull(componentName);
+ Objects.requireNonNull(componentName);
final String packageName = componentName.getPackageName();
final ArraySet<ComponentName> whitelistedComponents = getWhitelistedComponents(packageName);
@@ -136,7 +135,7 @@ public final class WhitelistHelper {
*/
@Nullable
public ArraySet<ComponentName> getWhitelistedComponents(@NonNull String packageName) {
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
return mWhitelistedPackages == null ? null : mWhitelistedPackages.get(packageName);
}
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index d08930b007ff..a22615ba19ef 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -32,6 +32,7 @@ import com.android.internal.util.Preconditions;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ThreadFactory;
public class FuseAppLoop implements Handler.Callback {
@@ -92,8 +93,8 @@ public class FuseAppLoop implements Handler.Callback {
public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
@NonNull Handler handler) throws FuseUnavailableMountException {
synchronized (mLock) {
- Preconditions.checkNotNull(callback);
- Preconditions.checkNotNull(handler);
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(handler);
Preconditions.checkState(
mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
Preconditions.checkArgument(
@@ -333,8 +334,8 @@ public class FuseAppLoop implements Handler.Callback {
boolean opened;
CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
- this.callback = Preconditions.checkNotNull(callback);
- this.handler = Preconditions.checkNotNull(handler);
+ this.callback = Objects.requireNonNull(callback);
+ this.handler = Objects.requireNonNull(handler);
}
long getThreadId() {
@@ -368,7 +369,7 @@ public class FuseAppLoop implements Handler.Callback {
void stopUsing(long threadId) {
final BytesMapEntry entry = mEntries.get(threadId);
- Preconditions.checkNotNull(entry);
+ Objects.requireNonNull(entry);
entry.counter--;
if (entry.counter <= 0) {
mEntries.remove(threadId);
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2b988c155412..c390a518bb22 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -151,6 +151,9 @@ public final class Zygote {
/** Make the new process have top application priority. */
public static final String START_AS_TOP_APP_ARG = "--is-top-app";
+ /** List of packages with the same uid, and its app data info: volume uuid and inode. */
+ public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+
/**
* An extraArg passed when a zygote process is forking a child-zygote, specifying a name
* in the abstract socket namespace. This socket name is what the new child zygote
@@ -254,6 +257,8 @@ public final class Zygote {
* @param instructionSet null-ok the instruction set to use.
* @param appDataDir null-ok the data directory of the app.
* @param isTopApp true if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*
* @return 0 if this is the child, pid of the child
* if this is the parent, or -1 on error.
@@ -261,12 +266,13 @@ public final class Zygote {
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- int targetSdkVersion, boolean isTopApp) {
+ int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp);
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Zygote.disableExecuteOnly(targetSdkVersion);
@@ -286,7 +292,7 @@ public final class Zygote {
private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
- String appDataDir, boolean isTopApp);
+ String appDataDir, boolean isTopApp, String[] pkgDataInfoList);
/**
* Specialize an unspecialized app process. The current VM must have been started
@@ -309,12 +315,16 @@ public final class Zygote {
* @param instructionSet null-ok The instruction set to use.
* @param appDataDir null-ok The data directory of the app.
* @param isTopApp True if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp) {
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
- niceName, startChildZygote, instructionSet, appDataDir, isTopApp);
+ niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -336,7 +346,8 @@ public final class Zygote {
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp);
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList);
/**
* Called to do any initialization before starting an application.
@@ -665,7 +676,8 @@ public final class Zygote {
specializeAppProcess(args.mUid, args.mGid, args.mGids,
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
- args.mInstructionSet, args.mAppDataDir, args.mIsTopApp);
+ args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
+ args.mPkgDataInfoList);
disableExecuteOnly(args.mTargetSdkVersion);
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 54b2a2063451..d3499541a3a3 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -221,6 +221,12 @@ class ZygoteArguments {
long[] mDisabledCompatChanges = null;
/**
+ * A list that stores all related packages and its data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mPkgDataInfoList;
+
+ /**
* Constructs instance and parses args
*
* @param args zygote command-line args
@@ -437,6 +443,8 @@ class ZygoteArguments {
for (int i = 0; i < length; i++) {
mDisabledCompatChanges[i] = Long.parseLong(params[i]);
}
+ } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
+ mPkgDataInfoList = getAssignmentList(arg);
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 3111b6ff0f72..9c6a288372c6 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,7 @@ class ZygoteConnection {
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
- parsedArgs.mIsTopApp);
+ parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList);
try {
if (pid == 0) {
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 01f57438a02c..cb67309ce74f 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -43,7 +43,6 @@ oneway interface IPhoneStateListener {
void onDataConnectionStateChanged(int state, int networkType);
void onDataActivity(int direction);
void onSignalStrengthsChanged(in SignalStrength signalStrength);
- void onOtaspChanged(in int otaspMode);
void onCellInfoChanged(in List<CellInfo> cellInfo);
void onPreciseCallStateChanged(in PreciseCallState callState);
void onPreciseDataConnectionStateChanged(in PreciseDataConnectionState dataConnectionState);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index d8e8d67c806d..f954679ebf4d 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -24,6 +24,8 @@ import android.telephony.CallQuality;
import android.telephony.CellInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.PreciseDataConnectionState;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -60,16 +62,13 @@ interface ITelephonyRegistry {
@UnsupportedAppUsage(maxTargetSdk = 28)
void notifyDataActivity(int state);
void notifyDataActivityForSubscriber(in int subId, int state);
- void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
- boolean isDataConnectivityPossible,
- String apn, String apnType, in LinkProperties linkProperties,
- in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
- void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType);
+ void notifyDataConnectionForSubscriber(
+ int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
+ @UnsupportedAppUsage
+ void notifyDataConnectionFailed(String apnType);
@UnsupportedAppUsage(maxTargetSdk = 28)
void notifyCellLocation(in Bundle cellLocation);
void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
- @UnsupportedAppUsage(maxTargetSdk = 28)
- void notifyOtaspChanged(in int subId, in int otaspMode);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 4165f202998c..4dac5427095b 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -308,6 +308,17 @@ public class CollectionUtils {
}
/**
+ * @see #add(List, Object)
+ */
+ public static @NonNull <K, V> Map<K, V> add(@Nullable Map<K, V> map, K key, V value) {
+ if (map == null || map == Collections.emptyMap()) {
+ map = new ArrayMap<>();
+ }
+ map.put(key, value);
+ return map;
+ }
+
+ /**
* Similar to {@link List#remove}, but with support for list values of {@code null} and
* {@link Collections#emptyList}
*/
diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java
index f8885a20970d..3ca33203f554 100644
--- a/core/java/com/android/internal/util/FileRotator.java
+++ b/core/java/com/android/internal/util/FileRotator.java
@@ -27,6 +27,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -96,8 +97,8 @@ public class FileRotator {
* may be deleted.
*/
public FileRotator(File basePath, String prefix, long rotateAgeMillis, long deleteAgeMillis) {
- mBasePath = Preconditions.checkNotNull(basePath);
- mPrefix = Preconditions.checkNotNull(prefix);
+ mBasePath = Objects.requireNonNull(basePath);
+ mPrefix = Objects.requireNonNull(prefix);
mRotateAgeMillis = rotateAgeMillis;
mDeleteAgeMillis = deleteAgeMillis;
@@ -406,7 +407,7 @@ public class FileRotator {
public long endMillis;
public FileInfo(String prefix) {
- this.prefix = Preconditions.checkNotNull(prefix);
+ this.prefix = Objects.requireNonNull(prefix);
}
/**
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index a47768870dfe..5568d91011dc 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -19,6 +19,7 @@ package com.android.internal.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.util.Objects;
/** @hide */
public class ObjectUtils {
@@ -32,7 +33,7 @@ public class ObjectUtils {
*/
@NonNull
public static <T> T firstNotNull(@Nullable T a, @NonNull T b) {
- return a != null ? a : Preconditions.checkNotNull(b);
+ return a != null ? a : Objects.requireNonNull(b);
}
/**
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index c7502ef04f1b..8446bbd09df7 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -25,7 +25,6 @@ import android.util.Pools;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.DecConsumer;
import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.DecPredicate;
@@ -545,7 +544,7 @@ final class PooledLambdaImpl<R> extends OmniFunction<Object,
+ ", k = " + k
+ ")");
}
- r.mFunc = Preconditions.checkNotNull(func);
+ r.mFunc = Objects.requireNonNull(func);
r.setFlags(MASK_FUNC_TYPE, LambdaType.encode(fNumArgs, fReturnType));
r.setFlags(MASK_EXPOSED_AS, LambdaType.encode(numPlaceholders, fReturnType));
if (ArrayUtils.size(r.mArgs) < fNumArgs) r.mArgs = new Object[fNumArgs];
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 54dede6753e4..f9e98e7eb473 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -33,11 +33,11 @@ import android.view.WindowManager;
import android.widget.PopupWindow;
import com.android.internal.R;
-import com.android.internal.util.Preconditions;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.FloatingToolbar;
import java.util.Arrays;
+import java.util.Objects;
public final class FloatingActionMode extends ActionMode {
@@ -84,8 +84,8 @@ public final class FloatingActionMode extends ActionMode {
public FloatingActionMode(
Context context, ActionMode.Callback2 callback,
View originatingView, FloatingToolbar floatingToolbar) {
- mContext = Preconditions.checkNotNull(context);
- mCallback = Preconditions.checkNotNull(callback);
+ mContext = Objects.requireNonNull(context);
+ mCallback = Objects.requireNonNull(callback);
mMenu = new MenuBuilder(context).setDefaultShowAsAction(
MenuItem.SHOW_AS_ACTION_IF_ROOM);
setType(ActionMode.TYPE_FLOATING);
@@ -107,14 +107,14 @@ public final class FloatingActionMode extends ActionMode {
mViewRectOnScreen = new Rect();
mPreviousViewRectOnScreen = new Rect();
mScreenRect = new Rect();
- mOriginatingView = Preconditions.checkNotNull(originatingView);
+ mOriginatingView = Objects.requireNonNull(originatingView);
mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
// Allow the content rect to overshoot a little bit beyond the
// bottom view bound if necessary.
mBottomAllowance = context.getResources()
.getDimensionPixelSize(R.dimen.content_rect_bottom_clip_allowance);
mDisplaySize = new Point();
- setFloatingToolbar(Preconditions.checkNotNull(floatingToolbar));
+ setFloatingToolbar(Objects.requireNonNull(floatingToolbar));
}
private void setFloatingToolbar(FloatingToolbar floatingToolbar) {
@@ -328,7 +328,7 @@ public final class FloatingActionMode extends ActionMode {
private long mLastShowTime;
public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
- mToolbar = Preconditions.checkNotNull(toolbar);
+ mToolbar = Objects.requireNonNull(toolbar);
}
public void activate() {
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 2ee902ab6468..58aaa80b51be 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -16,13 +16,16 @@
package com.android.internal.view;
+import android.content.ComponentName;
import android.os.IBinder;
import android.os.ResultReceiver;
+import android.view.autofill.AutofillId;
import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.IInputSessionCallback;
@@ -35,6 +38,9 @@ import com.android.internal.view.IInputSessionCallback;
oneway interface IInputMethod {
void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps);
+ void onCreateInlineSuggestionsRequest(in ComponentName componentName, in AutofillId autofillId,
+ in IInlineSuggestionsRequestCallback cb);
+
void bindInput(in InputBinding binding);
void unbindInput();
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index d618f6758faa..1979e4fe7a90 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -35,7 +35,7 @@ import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.PopupWindow.OnDismissListener;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* A standard menu popup in which when a submenu is opened, it replaces its parent menu in the
@@ -113,7 +113,7 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On
public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
int popupStyleRes, boolean overflowOnly) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index bb7423a67754..d7611dcd0aa0 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -155,7 +155,7 @@ public final class FloatingToolbar {
// TODO(b/65172902): Pass context in constructor when DecorView (and other callers)
// supports multi-display.
mContext = applyDefaultTheme(window.getContext());
- mWindow = Preconditions.checkNotNull(window);
+ mWindow = Objects.requireNonNull(window);
mPopup = new FloatingToolbarPopup(mContext, window.getDecorView());
}
@@ -165,7 +165,7 @@ public final class FloatingToolbar {
* toolbar.
*/
public FloatingToolbar setMenu(Menu menu) {
- mMenu = Preconditions.checkNotNull(menu);
+ mMenu = Objects.requireNonNull(menu);
return this;
}
@@ -189,7 +189,7 @@ public final class FloatingToolbar {
* toolbar.
*/
public FloatingToolbar setContentRect(Rect rect) {
- mContentRect.set(Preconditions.checkNotNull(rect));
+ mContentRect.set(Objects.requireNonNull(rect));
return this;
}
@@ -457,8 +457,8 @@ public final class FloatingToolbar {
* from.
*/
public FloatingToolbarPopup(Context context, View parent) {
- mParent = Preconditions.checkNotNull(parent);
- mContext = Preconditions.checkNotNull(context);
+ mParent = Objects.requireNonNull(parent);
+ mContext = Objects.requireNonNull(context);
mContentContainer = createContentContainer(context);
mPopupWindow = createPopupWindow(mContentContainer);
mMarginHorizontal = parent.getResources()
@@ -578,7 +578,7 @@ public final class FloatingToolbar {
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
*/
public void show(Rect contentRectOnScreen) {
- Preconditions.checkNotNull(contentRectOnScreen);
+ Objects.requireNonNull(contentRectOnScreen);
if (isShowing()) {
return;
@@ -650,7 +650,7 @@ public final class FloatingToolbar {
* This is a no-op if this popup is not showing.
*/
public void updateCoordinates(Rect contentRectOnScreen) {
- Preconditions.checkNotNull(contentRectOnScreen);
+ Objects.requireNonNull(contentRectOnScreen);
if (!isShowing() || !mPopupWindow.isShowing()) {
return;
@@ -1134,11 +1134,11 @@ public final class FloatingToolbar {
* Sets the touchable region of this popup to be the area occupied by its content.
*/
private void setContentAreaAsTouchableSurface() {
- Preconditions.checkNotNull(mMainPanelSize);
+ Objects.requireNonNull(mMainPanelSize);
final int width;
final int height;
if (mIsOverflowOpen) {
- Preconditions.checkNotNull(mOverflowPanelSize);
+ Objects.requireNonNull(mOverflowPanelSize);
width = mOverflowPanelSize.getWidth();
height = mOverflowPanelSize.getHeight();
} else {
@@ -1183,7 +1183,7 @@ public final class FloatingToolbar {
*/
public List<MenuItem> layoutMainPanelItems(
List<MenuItem> menuItems, final int toolbarWidth) {
- Preconditions.checkNotNull(menuItems);
+ Objects.requireNonNull(menuItems);
int availableWidth = toolbarWidth;
@@ -1555,7 +1555,7 @@ public final class FloatingToolbar {
private final FloatingToolbarPopup mPopup;
OverflowPanel(FloatingToolbarPopup popup) {
- super(Preconditions.checkNotNull(popup).mContext);
+ super(Objects.requireNonNull(popup).mContext);
this.mPopup = popup;
setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
@@ -1616,7 +1616,7 @@ public final class FloatingToolbar {
private final Context mContext;
public OverflowPanelViewHelper(Context context, int iconTextSpacing) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mIconTextSpacing = iconTextSpacing;
mSidePadding = context.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
@@ -1624,7 +1624,7 @@ public final class FloatingToolbar {
}
public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
- Preconditions.checkNotNull(menuItem);
+ Objects.requireNonNull(menuItem);
if (convertView != null) {
updateMenuItemButton(
convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index f456349a8937..9b87dd2c5f8b 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -33,6 +33,7 @@ import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* A class representing a lockscreen credential. It can be either an empty password, a pattern
@@ -67,7 +68,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
* minimize the number of extra copies introduced.
*/
private LockscreenCredential(int type, byte[] credential) {
- Preconditions.checkNotNull(credential);
+ Objects.requireNonNull(credential);
if (type == CREDENTIAL_TYPE_NONE) {
Preconditions.checkArgument(credential.length == 0);
} else {
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 54f25d341d09..8a59c998dacb 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -229,7 +229,7 @@ public class SystemConfig {
* Map of system pre-defined, uniquely named actors; keys are namespace,
* value maps actor name to package name.
*/
- private ArrayMap<String, ArrayMap<String, String>> mNamedActors = null;
+ private Map<String, Map<String, String>> mNamedActors = null;
public static SystemConfig getInstance() {
if (!isSystemProcess()) {
@@ -413,7 +413,7 @@ public class SystemConfig {
}
@NonNull
- public Map<String, ? extends Map<String, String>> getNamedActors() {
+ public Map<String, Map<String, String>> getNamedActors() {
return mNamedActors != null ? mNamedActors : Collections.emptyMap();
}
@@ -1083,7 +1083,7 @@ public class SystemConfig {
mNamedActors = new ArrayMap<>();
}
- ArrayMap<String, String> nameToPkgMap = mNamedActors.get(namespace);
+ Map<String, String> nameToPkgMap = mNamedActors.get(namespace);
if (nameToPkgMap == null) {
nameToPkgMap = new ArrayMap<>();
mNamedActors.put(namespace, nameToPkgMap);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b91d3595fe6a..60c5bf1529a6 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -110,7 +110,6 @@ cc_library_shared {
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_FrameMetricsObserver.cpp",
"android_view_KeyCharacterMap.cpp",
"android_view_KeyEvent.cpp",
"android_view_MotionEvent.cpp",
@@ -177,7 +176,6 @@ cc_library_shared {
"android_hardware_HardwareBuffer.cpp",
"android_hardware_SensorManager.cpp",
"android_hardware_SerialPort.cpp",
- "android_hardware_SoundTrigger.cpp",
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
@@ -259,7 +257,6 @@ cc_library_shared {
"libpdfium",
"libimg_utils",
"libnetd_client",
- "libsoundtrigger",
"libprocessgroup",
"libnativebridge_lazy",
"libnativeloader_lazy",
@@ -353,6 +350,7 @@ cc_library_static {
"android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
"android_graphics_drawable_VectorDrawable.cpp",
+ "android_graphics_HardwareRendererObserver.cpp",
"android_graphics_Picture.cpp",
"android_nio_utils.cpp",
"android_view_DisplayListCanvas.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b8fd3ad43ce5..3cde887ba465 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -80,7 +80,6 @@ extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
-extern int register_android_hardware_SoundTrigger(JNIEnv *env);
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -130,6 +129,7 @@ extern int register_android_database_CursorWindow(JNIEnv* env);
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_media_MediaMetrics(JNIEnv *env);
extern int register_android_os_Debug(JNIEnv* env);
extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
extern int register_android_os_HidlSupport(JNIEnv* env);
@@ -1508,7 +1508,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
- REG_JNI(register_android_hardware_SoundTrigger),
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
@@ -1522,6 +1521,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+ REG_JNI(register_android_media_MediaMetrics),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
diff --git a/core/jni/android/graphics/apex/jni_runtime.cpp b/core/jni/android/graphics/apex/jni_runtime.cpp
index 7f9bac0df44a..1f661534ad81 100644
--- a/core/jni/android/graphics/apex/jni_runtime.cpp
+++ b/core/jni/android/graphics/apex/jni_runtime.cpp
@@ -52,6 +52,7 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
@@ -71,7 +72,6 @@ extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
-extern int register_android_view_FrameMetricsObserver(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_TextureLayer(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
@@ -105,6 +105,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_ColorFilter),
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_FontFamily),
+ REG_JNI(register_android_graphics_HardwareRendererObserver),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
REG_JNI(register_android_graphics_Interpolator),
@@ -135,7 +136,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_DisplayListCanvas),
- REG_JNI(register_android_view_FrameMetricsObserver),
REG_JNI(register_android_view_TextureLayer),
REG_JNI(register_android_view_ThreadedRenderer),
};
diff --git a/core/jni/android_graphics_HardwareRendererObserver.cpp b/core/jni/android_graphics_HardwareRendererObserver.cpp
new file mode 100644
index 000000000000..89b77b0b069a
--- /dev/null
+++ b/core/jni/android_graphics_HardwareRendererObserver.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#include "android_graphics_HardwareRendererObserver.h"
+
+#include "core_jni_helpers.h"
+#include "nativehelper/jni_macros.h"
+
+#include <array>
+
+namespace android {
+
+struct {
+ jmethodID callback;
+} gHardwareRendererObserverClassInfo;
+
+static JNIEnv* getenv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
+ }
+ return env;
+}
+
+HardwareRendererObserver::HardwareRendererObserver(JavaVM *vm, jobject observer) : mVm(vm) {
+ mObserverWeak = getenv(mVm)->NewWeakGlobalRef(observer);
+ LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
+ "unable to create frame stats observer reference");
+}
+
+HardwareRendererObserver::~HardwareRendererObserver() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mObserverWeak);
+}
+
+bool HardwareRendererObserver::getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount) {
+ jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(metrics));
+ LOG_ALWAYS_FATAL_IF(bufferSize != HardwareRendererObserver::kBufferSize,
+ "Mismatched Java/Native FrameMetrics data format.");
+
+ FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
+ if (elem.hasData.load()) {
+ env->SetLongArrayRegion(metrics, 0, kBufferSize, elem.buffer);
+ *dropCount = elem.dropCount;
+ mNextInQueue = (mNextInQueue + 1) % kRingSize;
+ elem.hasData = false;
+ return true;
+ }
+
+ return false;
+}
+
+void HardwareRendererObserver::notify(const int64_t* stats) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextFree];
+
+ if (!elem.hasData.load()) {
+ memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
+
+ elem.dropCount = mDroppedReports;
+ mDroppedReports = 0;
+ mNextFree = (mNextFree + 1) % kRingSize;
+ elem.hasData = true;
+
+ JNIEnv* env = getenv(mVm);
+ jobject target = env->NewLocalRef(mObserverWeak);
+ if (target != nullptr) {
+ env->CallVoidMethod(target, gHardwareRendererObserverClassInfo.callback);
+ env->DeleteLocalRef(target);
+ }
+ } else {
+ mDroppedReports++;
+ }
+}
+
+static jlong android_graphics_HardwareRendererObserver_createObserver(JNIEnv* env,
+ jobject observerObj) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Unable to get Java VM");
+ return 0;
+ }
+
+ HardwareRendererObserver* observer = new HardwareRendererObserver(vm, observerObj);
+ return reinterpret_cast<jlong>(observer);
+}
+
+static jint android_graphics_HardwareRendererObserver_getNextBuffer(JNIEnv* env, jobject,
+ jlong observerPtr,
+ jlongArray metrics) {
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
+ int dropCount = 0;
+ if (observer->getNextBuffer(env, metrics, &dropCount)) {
+ return dropCount;
+ } else {
+ return -1;
+ }
+}
+
+static const std::array gMethods = {
+ MAKE_JNI_NATIVE_METHOD("nCreateObserver", "()J",
+ android_graphics_HardwareRendererObserver_createObserver),
+ MAKE_JNI_NATIVE_METHOD("nGetNextBuffer", "(J[J)I",
+ android_graphics_HardwareRendererObserver_getNextBuffer),
+};
+
+int register_android_graphics_HardwareRendererObserver(JNIEnv* env) {
+
+ jclass observerClass = FindClassOrDie(env, "android/graphics/HardwareRendererObserver");
+ gHardwareRendererObserverClassInfo.callback = GetMethodIDOrDie(env, observerClass,
+ "notifyDataAvailable", "()V");
+
+ return RegisterMethodsOrDie(env, "android/graphics/HardwareRendererObserver",
+ gMethods.data(), gMethods.size());
+
+}
+
+} // namespace android \ No newline at end of file
diff --git a/core/jni/android_graphics_HardwareRendererObserver.h b/core/jni/android_graphics_HardwareRendererObserver.h
new file mode 100644
index 000000000000..62111fd7d7a1
--- /dev/null
+++ b/core/jni/android_graphics_HardwareRendererObserver.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+#include <FrameInfo.h>
+#include <FrameMetricsObserver.h>
+
+namespace android {
+
+/*
+ * Implements JNI layer for hwui frame metrics reporting.
+ */
+class HardwareRendererObserver : public uirenderer::FrameMetricsObserver {
+public:
+ HardwareRendererObserver(JavaVM *vm, jobject observer);
+ ~HardwareRendererObserver();
+
+ /**
+ * Retrieves frame metrics for the oldest frame that the renderer has retained. The renderer
+ * will retain a buffer until it has been retrieved, via this method, or its internal storage
+ * is exhausted at which point it informs the caller of how many frames it has failed to store
+ * since the last time this method was invoked.
+ * @param env java env required to populate the provided buffer array
+ * @param metrics output parameter that represents the buffer of metrics that is to be filled
+ * @param dropCount output parameter that is updated to reflect the number of buffers that were
+ discarded since the last successful invocation of this method.
+ * @return true if there was data to populate the array and false otherwise. If false then
+ * neither the metrics buffer or dropCount will be modified.
+ */
+ bool getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount);
+
+ void notify(const int64_t* stats) override;
+
+private:
+ static constexpr int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes);
+ static constexpr int kRingSize = 3;
+
+ class FrameMetricsNotification {
+ public:
+ FrameMetricsNotification() {}
+
+ std::atomic_bool hasData = false;
+ int64_t buffer[kBufferSize];
+ int dropCount = 0;
+ private:
+ // non-copyable
+ FrameMetricsNotification(const FrameMetricsNotification&) = delete;
+ FrameMetricsNotification& operator=(const FrameMetricsNotification& ) = delete;
+ };
+
+ JavaVM* const mVm;
+ jweak mObserverWeak;
+
+ int mNextFree = 0;
+ int mNextInQueue = 0;
+ FrameMetricsNotification mRingBuffer[kRingSize];
+
+ int mDroppedReports = 0;
+};
+
+} // namespace android
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
deleted file mode 100644
index 4376b0b4ac42..000000000000
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ /dev/null
@@ -1,1071 +0,0 @@
-/*
-**
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundTrigger-JNI"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include "core_jni_helpers.h"
-#include <system/sound_trigger.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-#include <soundtrigger/SoundTrigger.h>
-#include <utils/RefBase.h>
-#include <utils/Vector.h>
-#include <binder/IMemory.h>
-#include <binder/MemoryDealer.h>
-#include "android_media_AudioFormat.h"
-
-using namespace android;
-
-static jclass gArrayListClass;
-static struct {
- jmethodID add;
-} gArrayListMethods;
-
-static jclass gUUIDClass;
-static struct {
- jmethodID toString;
-} gUUIDMethods;
-
-static const char* const kUnsupportedOperationExceptionClassPathName =
- "java/lang/UnsupportedOperationException";
-static jclass gUnsupportedOperationExceptionClass;
-static const char* const kIllegalArgumentExceptionClassPathName =
- "java/lang/IllegalArgumentException";
-static jclass gIllegalArgumentExceptionClass;
-
-static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
-static jclass gSoundTriggerClass;
-
-static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule";
-static jclass gModuleClass;
-static struct {
- jfieldID mNativeContext;
- jfieldID mId;
-} gModuleFields;
-static jmethodID gPostEventFromNative;
-
-static const char* const kModulePropertiesClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModuleProperties";
-static jclass gModulePropertiesClass;
-static jmethodID gModulePropertiesCstor;
-
-static const char* const kSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModel";
-static jclass gSoundModelClass;
-static struct {
- jfieldID uuid;
- jfieldID vendorUuid;
- jfieldID data;
-} gSoundModelFields;
-
-static const char* const kGenericSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel";
-static jclass gGenericSoundModelClass;
-
-static const char* const kKeyphraseClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$Keyphrase";
-static jclass gKeyphraseClass;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID locale;
- jfieldID text;
- jfieldID users;
-} gKeyphraseFields;
-
-static const char* const kKeyphraseSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseSoundModel";
-static jclass gKeyphraseSoundModelClass;
-static struct {
- jfieldID keyphrases;
-} gKeyphraseSoundModelFields;
-
-static const char* const kModelParamRangeClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModelParamRange";
-static jclass gModelParamRangeClass;
-static jmethodID gModelParamRangeCstor;
-
-static const char* const kRecognitionConfigClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionConfig";
-static jclass gRecognitionConfigClass;
-static struct {
- jfieldID captureRequested;
- jfieldID keyphrases;
- jfieldID data;
-} gRecognitionConfigFields;
-
-static const char* const kRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent";
-static jclass gRecognitionEventClass;
-static jmethodID gRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionEvent";
-static jclass gKeyphraseRecognitionEventClass;
-static jmethodID gKeyphraseRecognitionEventCstor;
-
-static const char* const kGenericRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent";
-static jclass gGenericRecognitionEventClass;
-static jmethodID gGenericRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionExtraClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra";
-static jclass gKeyphraseRecognitionExtraClass;
-static jmethodID gKeyphraseRecognitionExtraCstor;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID coarseConfidenceLevel;
- jfieldID confidenceLevels;
-} gKeyphraseRecognitionExtraFields;
-
-static const char* const kConfidenceLevelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ConfidenceLevel";
-static jclass gConfidenceLevelClass;
-static jmethodID gConfidenceLevelCstor;
-static struct {
- jfieldID userId;
- jfieldID confidenceLevel;
-} gConfidenceLevelFields;
-
-static const char* const kAudioFormatClassPathName =
- "android/media/AudioFormat";
-static jclass gAudioFormatClass;
-static jmethodID gAudioFormatCstor;
-
-static const char* const kSoundModelEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
-static jclass gSoundModelEventClass;
-static jmethodID gSoundModelEventCstor;
-
-static Mutex gLock;
-
-enum {
- SOUNDTRIGGER_STATUS_OK = 0,
- SOUNDTRIGGER_STATUS_ERROR = INT_MIN,
- SOUNDTRIGGER_PERMISSION_DENIED = -1,
- SOUNDTRIGGER_STATUS_NO_INIT = -19,
- SOUNDTRIGGER_STATUS_BAD_VALUE = -22,
- SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32,
- SOUNDTRIGGER_INVALID_OPERATION = -38,
-};
-
-enum {
- SOUNDTRIGGER_EVENT_RECOGNITION = 1,
- SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
- SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
-};
-
-static jint throwUnsupportedOperationException(JNIEnv *env)
-{
- return env->ThrowNew(gUnsupportedOperationExceptionClass, nullptr);
-}
-
-static jint throwIllegalArgumentException(JNIEnv *env)
-{
- return env->ThrowNew(gIllegalArgumentExceptionClass, nullptr);
-}
-
-// ----------------------------------------------------------------------------
-// ref-counted object for callbacks
-class JNISoundTriggerCallback: public SoundTriggerCallback
-{
-public:
- JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
- ~JNISoundTriggerCallback();
-
- virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
- virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
- virtual void onServiceStateChange(sound_trigger_service_state_t state);
- virtual void onServiceDied();
-
-private:
- jclass mClass; // Reference to SoundTrigger class
- jobject mObject; // Weak ref to SoundTrigger Java object to call on
-};
-
-JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
-{
-
- // Hold onto the SoundTriggerModule class for use in calling the static method
- // that posts events to the application thread.
- jclass clazz = env->GetObjectClass(thiz);
- if (clazz == NULL) {
- ALOGE("Can't find class %s", kModuleClassPathName);
- return;
- }
- mClass = (jclass)env->NewGlobalRef(clazz);
-
- // We use a weak reference so the SoundTriggerModule object can be garbage collected.
- // The reference is only used as a proxy for callbacks.
- mObject = env->NewGlobalRef(weak_thiz);
-}
-
-JNISoundTriggerCallback::~JNISoundTriggerCallback()
-{
- // remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->DeleteGlobalRef(mObject);
- env->DeleteGlobalRef(mClass);
-}
-
-void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jobject jAudioFormat = NULL;
- if (event->trigger_in_data || event->capture_available) {
- jint channelMask = (jint)audio_channel_mask_get_bits(event->audio_config.channel_mask);
- jint channelIndexMask = (jint)AUDIO_CHANNEL_NONE;
-
- switch (audio_channel_mask_get_representation(event->audio_config.channel_mask)) {
- case AUDIO_CHANNEL_REPRESENTATION_INDEX:
- channelIndexMask = channelMask;
- channelMask = (jint)AUDIO_CHANNEL_NONE;
- break;
- default:
- break;
- }
- jAudioFormat = env->NewObject(gAudioFormatClass,
- gAudioFormatCstor,
- audioFormatFromNative(event->audio_config.format),
- event->audio_config.sample_rate,
- channelMask,
- channelIndexMask);
-
- }
- if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_recognition_event *phraseEvent =
- (struct sound_trigger_phrase_recognition_event *)event;
-
- jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases,
- gKeyphraseRecognitionExtraClass, NULL);
- if (jExtras == NULL) {
- return;
- }
-
- for (size_t i = 0; i < phraseEvent->num_phrases; i++) {
- jobjectArray jConfidenceLevels = env->NewObjectArray(
- phraseEvent->phrase_extras[i].num_levels,
- gConfidenceLevelClass, NULL);
-
- if (jConfidenceLevels == NULL) {
- return;
- }
- for (size_t j = 0; j < phraseEvent->phrase_extras[i].num_levels; j++) {
- jobject jConfidenceLevel = env->NewObject(gConfidenceLevelClass,
- gConfidenceLevelCstor,
- phraseEvent->phrase_extras[i].levels[j].user_id,
- phraseEvent->phrase_extras[i].levels[j].level);
- env->SetObjectArrayElement(jConfidenceLevels, j, jConfidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
-
- jobject jNewExtra = env->NewObject(gKeyphraseRecognitionExtraClass,
- gKeyphraseRecognitionExtraCstor,
- phraseEvent->phrase_extras[i].id,
- phraseEvent->phrase_extras[i].recognition_modes,
- phraseEvent->phrase_extras[i].confidence_level,
- jConfidenceLevels);
-
- if (jNewExtra == NULL) {
- return;
- }
- env->SetObjectArrayElement(jExtras, i, jNewExtra);
- env->DeleteLocalRef(jNewExtra);
- env->DeleteLocalRef(jConfidenceLevels);
- }
- jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData, jExtras);
- env->DeleteLocalRef(jExtras);
- } else if (event->type == SOUND_MODEL_TYPE_GENERIC) {
- jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- } else {
- jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- }
-
- if (jAudioFormat != NULL) {
- env->DeleteLocalRef(jAudioFormat);
- }
- if (jData != NULL) {
- env->DeleteLocalRef(jData);
- }
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
-
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
- event->status, event->model, jData);
-
- env->DeleteLocalRef(jData);
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceDied()
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-// ----------------------------------------------------------------------------
-
-static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz)
-{
- Mutex::Autolock l(gLock);
- SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- return sp<SoundTrigger>(st);
-}
-
-static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module)
-{
- Mutex::Autolock l(gLock);
- sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- if (module.get()) {
- module->incStrong((void*)setSoundTrigger);
- }
- if (old != 0) {
- old->decStrong((void*)setSoundTrigger);
- }
- env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
- return old;
-}
-
-
-static jint
-android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
- jstring opPackageName, jobject jModules)
-{
- ALOGV("listModules");
-
- if (jModules == NULL) {
- ALOGE("listModules NULL AudioPatch ArrayList");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- if (!env->IsInstanceOf(jModules, gArrayListClass)) {
- ALOGE("listModules not an arraylist");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- unsigned int numModules = 0;
- struct sound_trigger_module_descriptor *nModules = NULL;
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- if (status != NO_ERROR || numModules == 0) {
- return (jint)status;
- }
-
- nModules = (struct sound_trigger_module_descriptor *)
- calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
-
- status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
-
- if (status != NO_ERROR) {
- numModules = 0;
- }
-
- for (size_t i = 0; i < numModules; i++) {
- char str[SOUND_TRIGGER_MAX_STRING_LEN];
-
- jstring implementor = env->NewStringUTF(nModules[i].properties.implementor);
- jstring description = env->NewStringUTF(nModules[i].properties.description);
- SoundTrigger::guidToString(&nModules[i].properties.uuid,
- str,
- SOUND_TRIGGER_MAX_STRING_LEN);
- jstring uuid = env->NewStringUTF(str);
-
- ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
- i, nModules[i].handle, nModules[i].properties.description,
- nModules[i].properties.max_sound_models);
-
- jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
- nModules[i].handle,
- implementor, description, uuid,
- nModules[i].properties.version,
- nModules[i].properties.max_sound_models,
- nModules[i].properties.max_key_phrases,
- nModules[i].properties.max_users,
- nModules[i].properties.recognition_modes,
- nModules[i].properties.capture_transition,
- nModules[i].properties.max_buffer_ms,
- nModules[i].properties.concurrent_capture,
- nModules[i].properties.power_consumption_mw,
- nModules[i].properties.trigger_in_event);
-
- env->DeleteLocalRef(implementor);
- env->DeleteLocalRef(description);
- env->DeleteLocalRef(uuid);
- if (newModuleDesc == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc);
- }
-
-exit:
- free(nModules);
- return (jint) status;
-}
-
-static void
-android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz,
- jstring opPackageName, jobject weak_this)
-{
- ALOGV("setup");
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
-
- sound_trigger_module_handle_t handle =
- (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
-
- sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback);
- if (module == 0) {
- return;
- }
-
- setSoundTrigger(env, thiz, module);
-}
-
-static void
-android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz)
-{
- ALOGV("detach");
- sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0);
- ALOGV("detach module %p", module.get());
- if (module != 0) {
- ALOGV("detach module->detach()");
- module->detach();
- }
-}
-
-static void
-android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("finalize");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module != 0) {
- ALOGW("SoundTrigger finalized without being detached");
- }
- android_hardware_SoundTrigger_detach(env, thiz);
-}
-
-static jint
-android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
- jobject jSoundModel, jintArray jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- jbyte *nData = NULL;
- struct sound_trigger_sound_model *nSoundModel;
- jbyteArray jData;
- sp<MemoryDealer> memoryDealer;
- sp<IMemory> memory;
- size_t size;
- sound_model_handle_t handle = 0;
- jobject jUuid;
- jstring jUuidString;
- const char *nUuidString;
-
- ALOGV("loadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (jHandle == NULL) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jsize jHandleLen = env->GetArrayLength(jHandle);
- if (jHandleLen == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jint *nHandle = env->GetIntArrayElements(jHandle, NULL);
- if (nHandle == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size_t offset;
- sound_trigger_sound_model_type_t type;
- if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) {
- offset = sizeof(struct sound_trigger_phrase_sound_model);
- type = SOUND_MODEL_TYPE_KEYPHRASE;
- } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) {
- offset = sizeof(struct sound_trigger_generic_sound_model);
- type = SOUND_MODEL_TYPE_GENERIC;
- } else {
- offset = sizeof(struct sound_trigger_sound_model);
- type = SOUND_MODEL_TYPE_UNKNOWN;
- }
-
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.uuid);
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- sound_trigger_uuid_t nUuid;
- SoundTrigger::stringToGuid(nUuidString, &nUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
-
- sound_trigger_uuid_t nVendorUuid;
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
- if (jUuid != NULL) {
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
- } else {
- SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
- }
-
- jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size = env->GetArrayLength(jData);
-
- nData = env->GetByteArrayElements(jData, NULL);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel");
- if (memoryDealer == 0) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- memory = memoryDealer->allocate(offset + size);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
-
- nSoundModel->type = type;
- nSoundModel->uuid = nUuid;
- nSoundModel->vendor_uuid = nVendorUuid;
- nSoundModel->data_size = size;
- nSoundModel->data_offset = offset;
- memcpy((char *)nSoundModel + offset, nData, size);
- if (type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_sound_model *phraseModel =
- (struct sound_trigger_phrase_sound_model *)nSoundModel;
-
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jSoundModel, gKeyphraseSoundModelFields.keyphrases);
- if (jPhrases == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
-
- size_t numPhrases = env->GetArrayLength(jPhrases);
- phraseModel->num_phrases = numPhrases;
- ALOGV("loadSoundModel numPhrases %zu", numPhrases);
- for (size_t i = 0; i < numPhrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- phraseModel->phrases[i].id =
- env->GetIntField(jPhrase,gKeyphraseFields.id);
- phraseModel->phrases[i].recognition_mode =
- env->GetIntField(jPhrase,gKeyphraseFields.recognitionModes);
-
- jintArray jUsers = (jintArray)env->GetObjectField(jPhrase, gKeyphraseFields.users);
- phraseModel->phrases[i].num_users = env->GetArrayLength(jUsers);
- jint *nUsers = env->GetIntArrayElements(jUsers, NULL);
- memcpy(phraseModel->phrases[i].users,
- nUsers,
- phraseModel->phrases[i].num_users * sizeof(int));
- env->ReleaseIntArrayElements(jUsers, nUsers, 0);
- env->DeleteLocalRef(jUsers);
-
- jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.locale);
- const char *nLocale = env->GetStringUTFChars(jLocale, NULL);
- strncpy(phraseModel->phrases[i].locale,
- nLocale,
- SOUND_TRIGGER_MAX_LOCALE_LEN);
- jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.text);
- const char *nText = env->GetStringUTFChars(jText, NULL);
- strncpy(phraseModel->phrases[i].text,
- nText,
- SOUND_TRIGGER_MAX_STRING_LEN);
-
- env->ReleaseStringUTFChars(jLocale, nLocale);
- env->DeleteLocalRef(jLocale);
- env->ReleaseStringUTFChars(jText, nText);
- env->DeleteLocalRef(jText);
- ALOGV("loadSoundModel phrases %zu text %s locale %s",
- i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
- } else if (type == SOUND_MODEL_TYPE_GENERIC) {
- /* No initialization needed */
- }
- status = module->loadSoundModel(memory, &handle);
- ALOGV("loadSoundModel status %d handle %d", status, handle);
-
-exit:
- if (nHandle != NULL) {
- nHandle[0] = (jint)handle;
- env->ReleaseIntArrayElements(jHandle, nHandle, NULL);
- }
- if (nData != NULL) {
- env->ReleaseByteArrayElements(jData, nData, NULL);
- }
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("unloadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->unloadSoundModel((sound_model_handle_t)jHandle);
-
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
- jint jHandle, jobject jConfig)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("startRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
-
- if (!env->IsInstanceOf(jConfig, gRecognitionConfigClass)) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- jbyteArray jData = (jbyteArray)env->GetObjectField(jConfig, gRecognitionConfigFields.data);
- jsize dataSize = 0;
- jbyte *nData = NULL;
- if (jData != NULL) {
- dataSize = env->GetArrayLength(jData);
- if (dataSize == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- nData = env->GetByteArrayElements(jData, NULL);
- if (nData == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- }
-
- size_t totalSize = sizeof(struct sound_trigger_recognition_config) + dataSize;
- sp<MemoryDealer> memoryDealer =
- new MemoryDealer(totalSize, "SoundTrigge-JNI::StartRecognition");
- if (memoryDealer == 0) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- sp<IMemory> memory = memoryDealer->allocate(totalSize);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (dataSize != 0) {
- memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
- nData,
- dataSize);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
- env->DeleteLocalRef(jData);
- struct sound_trigger_recognition_config *config =
- (struct sound_trigger_recognition_config *)memory->unsecurePointer();
- config->data_size = dataSize;
- config->data_offset = sizeof(struct sound_trigger_recognition_config);
- config->capture_requested = env->GetBooleanField(jConfig,
- gRecognitionConfigFields.captureRequested);
-
- config->num_phrases = 0;
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jConfig, gRecognitionConfigFields.keyphrases);
- if (jPhrases != NULL) {
- config->num_phrases = env->GetArrayLength(jPhrases);
- }
- ALOGV("startRecognition num phrases %d", config->num_phrases);
- for (size_t i = 0; i < config->num_phrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- config->phrases[i].id = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.id);
- config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.recognitionModes);
- config->phrases[i].confidence_level = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
- config->phrases[i].num_levels = 0;
- jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
- gKeyphraseRecognitionExtraFields.confidenceLevels);
- if (jConfidenceLevels != NULL) {
- config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
- }
- ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
- for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
- jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
- config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.userId);
- config->phrases[i].levels[j].level = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.confidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
- ALOGV("startRecognition phrases %zu", i);
- env->DeleteLocalRef(jConfidenceLevels);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
-
- status = module->startRecognition(jHandle, memory);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("stopRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->stopRecognition(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_getModelState(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("getModelState");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->getModelState(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_setParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam, jint jValue)
-{
- ALOGV("setParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_NO_INIT;
- }
- return module->setParameter(jHandle, (sound_trigger_model_parameter_t) jModelParam, jValue);
-}
-
-static jint
-android_hardware_SoundTrigger_getParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam)
-{
- ALOGV("getParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- throwUnsupportedOperationException(env);
- return -1;
- }
-
- jint nValue;
- jint status = module->getParameter(jHandle,
- (sound_trigger_model_parameter_t) jModelParam, &nValue);
-
- switch (status) {
- case 0:
- return nValue;
- case -EINVAL:
- throwIllegalArgumentException(env);
- break;
- default:
- throwUnsupportedOperationException(env);
- break;
- }
-
- return -1;
-}
-
-static jobject
-android_hardware_SoundTrigger_queryParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam)
-{
- ALOGV("queryParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == nullptr) {
- return nullptr;
- }
-
- sound_trigger_model_parameter_range_t nRange;
- jint nValue = module->queryParameter(jHandle,
- (sound_trigger_model_parameter_t) jModelParam, &nRange);
-
- if (nValue != 0) {
- ALOGE("failed to query parameter error code: %d", nValue);
- return nullptr;
- }
-
- return env->NewObject(gModelParamRangeClass, gModelParamRangeCstor, nRange.start, nRange.end);
-}
-
-static const JNINativeMethod gMethods[] = {
- {"listModules",
- "(Ljava/lang/String;Ljava/util/ArrayList;)I",
- (void *)android_hardware_SoundTrigger_listModules},
-};
-
-
-static const JNINativeMethod gModuleMethods[] = {
- {"native_setup",
- "(Ljava/lang/String;Ljava/lang/Object;)V",
- (void *)android_hardware_SoundTrigger_setup},
- {"native_finalize",
- "()V",
- (void *)android_hardware_SoundTrigger_finalize},
- {"detach",
- "()V",
- (void *)android_hardware_SoundTrigger_detach},
- {"loadSoundModel",
- "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I",
- (void *)android_hardware_SoundTrigger_loadSoundModel},
- {"unloadSoundModel",
- "(I)I",
- (void *)android_hardware_SoundTrigger_unloadSoundModel},
- {"startRecognition",
- "(ILandroid/hardware/soundtrigger/SoundTrigger$RecognitionConfig;)I",
- (void *)android_hardware_SoundTrigger_startRecognition},
- {"stopRecognition",
- "(I)I",
- (void *)android_hardware_SoundTrigger_stopRecognition},
- {"getModelState",
- "(I)I",
- (void *)android_hardware_SoundTrigger_getModelState},
- {"setParameter",
- "(III)I",
- (void *)android_hardware_SoundTrigger_setParameter},
- {"getParameter",
- "(II)I",
- (void *)android_hardware_SoundTrigger_getParameter},
- {"queryParameter",
- "(II)Landroid/hardware/soundtrigger/SoundTrigger$ModelParamRange;",
- (void *)android_hardware_SoundTrigger_queryParameter}
-};
-
-int register_android_hardware_SoundTrigger(JNIEnv *env)
-{
- jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
- gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
- gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
-
- jclass uuidClass = FindClassOrDie(env, "java/util/UUID");
- gUUIDClass = MakeGlobalRefOrDie(env, uuidClass);
- gUUIDMethods.toString = GetMethodIDOrDie(env, uuidClass, "toString", "()Ljava/lang/String;");
-
- jclass exUClass = FindClassOrDie(env, kUnsupportedOperationExceptionClassPathName);
- gUnsupportedOperationExceptionClass = MakeGlobalRefOrDie(env, exUClass);
-
- jclass exIClass = FindClassOrDie(env, kIllegalArgumentExceptionClassPathName);
- gIllegalArgumentExceptionClass = MakeGlobalRefOrDie(env, exIClass);
-
- jclass lClass = FindClassOrDie(env, kSoundTriggerClassPathName);
- gSoundTriggerClass = MakeGlobalRefOrDie(env, lClass);
-
- jclass moduleClass = FindClassOrDie(env, kModuleClassPathName);
- gModuleClass = MakeGlobalRefOrDie(env, moduleClass);
- gPostEventFromNative = GetStaticMethodIDOrDie(env, moduleClass, "postEventFromNative",
- "(Ljava/lang/Object;IIILjava/lang/Object;)V");
- gModuleFields.mNativeContext = GetFieldIDOrDie(env, moduleClass, "mNativeContext", "J");
- gModuleFields.mId = GetFieldIDOrDie(env, moduleClass, "mId", "I");
-
- jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
- gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
- gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
- "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
-
- jclass soundModelClass = FindClassOrDie(env, kSoundModelClassPathName);
- gSoundModelClass = MakeGlobalRefOrDie(env, soundModelClass);
- gSoundModelFields.uuid = GetFieldIDOrDie(env, soundModelClass, "uuid", "Ljava/util/UUID;");
- gSoundModelFields.vendorUuid = GetFieldIDOrDie(env, soundModelClass, "vendorUuid",
- "Ljava/util/UUID;");
- gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B");
-
- jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName);
- gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass);
-
- jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName);
- gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass);
- gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I");
- gKeyphraseFields.recognitionModes = GetFieldIDOrDie(env, keyphraseClass, "recognitionModes",
- "I");
- gKeyphraseFields.locale = GetFieldIDOrDie(env, keyphraseClass, "locale", "Ljava/lang/String;");
- gKeyphraseFields.text = GetFieldIDOrDie(env, keyphraseClass, "text", "Ljava/lang/String;");
- gKeyphraseFields.users = GetFieldIDOrDie(env, keyphraseClass, "users", "[I");
-
- jclass keyphraseSoundModelClass = FindClassOrDie(env, kKeyphraseSoundModelClassPathName);
- gKeyphraseSoundModelClass = MakeGlobalRefOrDie(env, keyphraseSoundModelClass);
- gKeyphraseSoundModelFields.keyphrases = GetFieldIDOrDie(env, keyphraseSoundModelClass,
- "keyphrases",
- "[Landroid/hardware/soundtrigger/SoundTrigger$Keyphrase;");
-
- jclass modelParamRangeClass = FindClassOrDie(env, kModelParamRangeClassPathName);
- gModelParamRangeClass = MakeGlobalRefOrDie(env, modelParamRangeClass);
- gModelParamRangeCstor = GetMethodIDOrDie(env, modelParamRangeClass, "<init>", "(II)V");
-
- jclass recognitionEventClass = FindClassOrDie(env, kRecognitionEventClassPathName);
- gRecognitionEventClass = MakeGlobalRefOrDie(env, recognitionEventClass);
- gRecognitionEventCstor = GetMethodIDOrDie(env, recognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyphraseRecognitionEventClass = FindClassOrDie(env,
- kKeyphraseRecognitionEventClassPathName);
- gKeyphraseRecognitionEventClass = MakeGlobalRefOrDie(env, keyphraseRecognitionEventClass);
- gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
-
- jclass genericRecognitionEventClass = FindClassOrDie(env,
- kGenericRecognitionEventClassPathName);
- gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass);
- gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName);
- gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass);
- gRecognitionConfigFields.captureRequested = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "captureRequested", "Z");
- gRecognitionConfigFields.keyphrases = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "keyphrases", "[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;");
- gRecognitionConfigFields.data = GetFieldIDOrDie(env, keyRecognitionConfigClass, "data", "[B");
-
- jclass keyphraseRecognitionExtraClass = FindClassOrDie(env,
- kKeyphraseRecognitionExtraClassPathName);
- gKeyphraseRecognitionExtraClass = MakeGlobalRefOrDie(env, keyphraseRecognitionExtraClass);
- gKeyphraseRecognitionExtraCstor = GetMethodIDOrDie(env, keyphraseRecognitionExtraClass,
- "<init>", "(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
- gKeyphraseRecognitionExtraFields.id = GetFieldIDOrDie(env, gKeyphraseRecognitionExtraClass,
- "id", "I");
- gKeyphraseRecognitionExtraFields.recognitionModes = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "coarseConfidenceLevel", "I");
- gKeyphraseRecognitionExtraFields.confidenceLevels = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "confidenceLevels",
- "[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
-
- jclass confidenceLevelClass = FindClassOrDie(env, kConfidenceLevelClassPathName);
- gConfidenceLevelClass = MakeGlobalRefOrDie(env, confidenceLevelClass);
- gConfidenceLevelCstor = GetMethodIDOrDie(env, confidenceLevelClass, "<init>", "(II)V");
- gConfidenceLevelFields.userId = GetFieldIDOrDie(env, confidenceLevelClass, "userId", "I");
- gConfidenceLevelFields.confidenceLevel = GetFieldIDOrDie(env, confidenceLevelClass,
- "confidenceLevel", "I");
-
- jclass audioFormatClass = FindClassOrDie(env, kAudioFormatClassPathName);
- gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
- gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIII)V");
-
- jclass soundModelEventClass = FindClassOrDie(env, kSoundModelEventClassPathName);
- gSoundModelEventClass = MakeGlobalRefOrDie(env, soundModelEventClass);
- gSoundModelEventCstor = GetMethodIDOrDie(env, soundModelEventClass, "<init>", "(II[B)V");
-
-
- RegisterMethodsOrDie(env, kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
- return RegisterMethodsOrDie(env, kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
-}
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 342aba024fc5..6cbc5878db61 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -763,7 +763,7 @@ android_media_AudioRecord_native_getMetrics(JNIEnv *env, jobject thiz)
}
// get what we have for the metrics from the record session
- MediaAnalyticsItem *item = NULL;
+ mediametrics::Item *item = NULL;
status_t err = lpRecord->getMetrics(item);
if (err != OK) {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index c5049ecd3784..c979133d2493 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1032,7 +1032,7 @@ android_media_AudioTrack_native_getMetrics(JNIEnv *env, jobject thiz)
}
// get what we have for the metrics from the track
- MediaAnalyticsItem *item = NULL;
+ mediametrics::Item *item = NULL;
status_t err = lpTrack->getMetrics(item);
if (err != OK) {
diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp
index 4c0f55f2bf23..381b386d7f62 100644
--- a/core/jni/android_service_DataLoaderService.cpp
+++ b/core/jni/android_service_DataLoaderService.cpp
@@ -16,83 +16,18 @@
#define LOG_TAG "dataloader-jni"
-#include <vector>
-
#include "core_jni_helpers.h"
#include "dataloader_ndk.h"
-#include "jni.h"
namespace android {
namespace {
-struct JniIds {
- jfieldID dataBlockFileIno;
- jfieldID dataBlockBlockIndex;
- jfieldID dataBlockDataBytes;
- jfieldID dataBlockCompressionType;
-
- JniIds(JNIEnv* env) {
- const auto dataBlock =
- FindClassOrDie(env,
- "android/service/incremental/"
- "IncrementalDataLoaderService$FileSystemConnector$DataBlock");
- dataBlockFileIno = GetFieldIDOrDie(env, dataBlock, "mFileIno", "J");
- dataBlockBlockIndex =
- GetFieldIDOrDie(env, dataBlock, "mBlockIndex", "I");
- dataBlockDataBytes = GetFieldIDOrDie(env, dataBlock, "mDataBytes", "[B");
- dataBlockCompressionType =
- GetFieldIDOrDie(env, dataBlock, "mCompressionType", "I");
- }
-};
-
-const JniIds& jniIds(JNIEnv* env) {
- static const JniIds ids(env);
- return ids;
-}
-
-class ScopedJniArrayCritical {
-public:
- ScopedJniArrayCritical(JNIEnv* env, jarray array) : mEnv(env), mArr(array) {
- mPtr = array ? env->GetPrimitiveArrayCritical(array, nullptr) : nullptr;
- }
- ~ScopedJniArrayCritical() {
- if (mPtr) {
- mEnv->ReleasePrimitiveArrayCritical(mArr, mPtr, 0);
- mPtr = nullptr;
- }
- }
-
- ScopedJniArrayCritical(const ScopedJniArrayCritical&) = delete;
- void operator=(const ScopedJniArrayCritical&) = delete;
-
- ScopedJniArrayCritical(ScopedJniArrayCritical&& other)
- : mEnv(other.mEnv),
- mArr(std::exchange(mArr, nullptr)),
- mPtr(std::exchange(mPtr, nullptr)) {}
- ScopedJniArrayCritical& operator=(ScopedJniArrayCritical&& other) {
- mEnv = other.mEnv;
- mArr = std::exchange(other.mArr, nullptr);
- mPtr = std::exchange(other.mPtr, nullptr);
- return *this;
- }
-
- void* ptr() const { return mPtr; }
- jsize size() const { return mArr ? mEnv->GetArrayLength(mArr) : 0; }
-
-private:
- JNIEnv* mEnv;
- jarray mArr;
- void* mPtr;
-};
-
static jboolean nativeCreateDataLoader(JNIEnv* env,
jobject thiz,
jint storageId,
jobject control,
jobject params,
jobject callback) {
- ALOGE("nativeCreateDataLoader: %p/%d, %d, %p, %p, %p", thiz,
- env->GetObjectRefType(thiz), storageId, params, control, callback);
return DataLoaderService_OnCreate(env, thiz,
storageId, control, params, callback);
}
@@ -100,130 +35,22 @@ static jboolean nativeCreateDataLoader(JNIEnv* env,
static jboolean nativeStartDataLoader(JNIEnv* env,
jobject thiz,
jint storageId) {
- ALOGE("nativeStartDataLoader: %p/%d, %d", thiz, env->GetObjectRefType(thiz),
- storageId);
return DataLoaderService_OnStart(storageId);
}
static jboolean nativeStopDataLoader(JNIEnv* env,
jobject thiz,
jint storageId) {
- ALOGE("nativeStopDataLoader: %p/%d, %d", thiz, env->GetObjectRefType(thiz),
- storageId);
return DataLoaderService_OnStop(storageId);
}
static jboolean nativeDestroyDataLoader(JNIEnv* env,
jobject thiz,
jint storageId) {
- ALOGE("nativeDestroyDataLoader: %p/%d, %d", thiz,
- env->GetObjectRefType(thiz), storageId);
return DataLoaderService_OnDestroy(storageId);
}
-static jboolean nativeOnFileCreated(JNIEnv* env,
- jobject thiz,
- jint storageId,
- jlong inode,
- jbyteArray metadata) {
- ALOGE("nativeOnFileCreated: %p/%d, %d", thiz,
- env->GetObjectRefType(thiz), storageId);
- return DataLoaderService_OnFileCreated(storageId, inode, metadata);
-}
-
-static jboolean nativeIsFileRangeLoadedNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jlong node,
- jlong start,
- jlong end) {
- // TODO(b/136132412): implement this
- return JNI_FALSE;
-}
-
-static jboolean nativeWriteMissingData(JNIEnv* env,
- jobject clazz,
- jlong self,
- jobjectArray data_block,
- jobjectArray hash_blocks) {
- const auto& jni = jniIds(env);
- auto length = env->GetArrayLength(data_block);
- std::vector<incfs_new_data_block> instructions(length);
-
- // May not call back into Java after even a single jniArrayCritical, so
- // let's collect the Java pointers to byte buffers first and lock them in
- // memory later.
-
- std::vector<jbyteArray> blockBuffers(length);
- for (int i = 0; i != length; ++i) {
- auto& inst = instructions[i];
- auto jniBlock = env->GetObjectArrayElement(data_block, i);
- inst.file_ino = env->GetLongField(jniBlock, jni.dataBlockFileIno);
- inst.block_index = env->GetIntField(jniBlock, jni.dataBlockBlockIndex);
- blockBuffers[i] = (jbyteArray)env->GetObjectField(
- jniBlock, jni.dataBlockDataBytes);
- inst.compression = (incfs_compression_alg)env->GetIntField(
- jniBlock, jni.dataBlockCompressionType);
- }
-
- std::vector<ScopedJniArrayCritical> jniScopedArrays;
- jniScopedArrays.reserve(length);
- for (int i = 0; i != length; ++i) {
- auto buffer = blockBuffers[i];
- jniScopedArrays.emplace_back(env, buffer);
- auto& inst = instructions[i];
- inst.data = (uint64_t)jniScopedArrays.back().ptr();
- inst.data_len = jniScopedArrays.back().size();
- }
-
- auto connector = (DataLoaderFilesystemConnectorPtr)self;
- if (auto err = DataLoader_FilesystemConnector_writeBlocks(
- connector, instructions.data(), length);
- err < 0) {
- jniScopedArrays.clear();
- return JNI_FALSE;
- }
-
- return JNI_TRUE;
-}
-
-static jboolean nativeWriteSignerDataNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jstring relative_path,
- jbyteArray signer_data) {
- // TODO(b/136132412): implement this
- return JNI_TRUE;
-}
-
-static jbyteArray nativeGetFileMetadataNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jlong inode) {
- auto connector = (DataLoaderFilesystemConnectorPtr)self;
- std::vector<char> metadata(INCFS_MAX_FILE_ATTR_SIZE);
- size_t size = metadata.size();
- if (DataLoader_FilesystemConnector_getRawMetadata(connector, inode,
- metadata.data(), &size) < 0) {
- size = 0;
- }
- metadata.resize(size);
-
- auto buffer = env->NewByteArray(metadata.size());
- env->SetByteArrayRegion(buffer, 0, metadata.size(),
- (jbyte*)metadata.data());
- return buffer;
-}
-
-static jbyteArray nativeGetFileInfoNode(JNIEnv* env,
- jobject clazz,
- jlong self,
- jlong inode) {
- // TODO(b/136132412): implement this
- return nullptr;
-}
-
static jboolean nativeReportStatus(JNIEnv* env,
jobject clazz,
jlong self,
@@ -235,34 +62,21 @@ static jboolean nativeReportStatus(JNIEnv* env,
static const JNINativeMethod dlc_method_table[] = {
{"nativeCreateDataLoader",
- "(ILandroid/os/incremental/IncrementalFileSystemControlParcel;"
- "Landroid/os/incremental/IncrementalDataLoaderParamsParcel;"
+ "(ILandroid/content/pm/FileSystemControlParcel;"
+ "Landroid/content/pm/DataLoaderParamsParcel;"
"Landroid/content/pm/IDataLoaderStatusListener;)Z",
(void*)nativeCreateDataLoader},
{"nativeStartDataLoader", "(I)Z", (void*)nativeStartDataLoader},
{"nativeStopDataLoader", "(I)Z", (void*)nativeStopDataLoader},
{"nativeDestroyDataLoader", "(I)Z", (void*)nativeDestroyDataLoader},
- {"nativeIsFileRangeLoadedNode", "(JJJJ)Z",
- (void*)nativeIsFileRangeLoadedNode},
- {"nativeWriteMissingData",
- "(J[Landroid/service/incremental/"
- "IncrementalDataLoaderService$FileSystemConnector$DataBlock;[Landroid/service/incremental/"
- "IncrementalDataLoaderService$FileSystemConnector$HashBlock;)Z",
- (void*)nativeWriteMissingData},
- {"nativeWriteSignerDataNode", "(JJ[B)Z",
- (void*)nativeWriteSignerDataNode},
- {"nativeGetFileMetadataNode", "(JJ)[B",
- (void*)nativeGetFileMetadataNode},
- {"nativeGetFileInfoNode", "(JJ)[B", (void*)nativeGetFileInfoNode},
{"nativeReportStatus", "(JI)Z", (void*)nativeReportStatus},
- {"nativeOnFileCreated", "(IJ[B)Z", (void*)nativeOnFileCreated},
};
} // namespace
int register_android_service_DataLoaderService(JNIEnv* env) {
return jniRegisterNativeMethods(env,
- "android/service/incremental/IncrementalDataLoaderService",
+ "android/service/dataloader/DataLoaderService",
dlc_method_table, NELEM(dlc_method_table));
}
diff --git a/core/jni/android_view_FrameMetricsObserver.cpp b/core/jni/android_view_FrameMetricsObserver.cpp
deleted file mode 100644
index febcb55bd0cd..000000000000
--- a/core/jni/android_view_FrameMetricsObserver.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.
- */
-
-#include "android_view_FrameMetricsObserver.h"
-
-namespace android {
-
-struct {
- jfieldID frameMetrics;
- jfieldID timingDataBuffer;
- jfieldID messageQueue;
- jmethodID callback;
-} gFrameMetricsObserverClassInfo;
-
-static JNIEnv* getenv(JavaVM* vm) {
- JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
- }
- return env;
-}
-
-static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
- jobject frameMetrics = env->GetObjectField(
- observer, gFrameMetricsObserverClassInfo.frameMetrics);
- LOG_ALWAYS_FATAL_IF(frameMetrics == nullptr, "unable to retrieve data sink object");
- jobject buffer = env->GetObjectField(
- frameMetrics, gFrameMetricsObserverClassInfo.timingDataBuffer);
- LOG_ALWAYS_FATAL_IF(buffer == nullptr, "unable to retrieve data sink buffer");
- return reinterpret_cast<jlongArray>(buffer);
-}
-
-class NotifyHandler : public MessageHandler {
-public:
- NotifyHandler(JavaVM* vm, FrameMetricsObserverProxy* observer) : mVm(vm), mObserver(observer) {}
-
- virtual void handleMessage(const Message& message);
-
-private:
- JavaVM* const mVm;
- FrameMetricsObserverProxy* const mObserver;
-};
-
-void NotifyHandler::handleMessage(const Message& message) {
- JNIEnv* env = getenv(mVm);
-
- jobject target = env->NewLocalRef(mObserver->getObserverReference());
-
- if (target != nullptr) {
- jlongArray javaBuffer = get_metrics_buffer(env, target);
- int dropCount = 0;
- while (mObserver->getNextBuffer(env, javaBuffer, &dropCount)) {
- env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback, dropCount);
- }
- env->DeleteLocalRef(target);
- }
-
- mObserver->decStrong(nullptr);
-}
-
-FrameMetricsObserverProxy::FrameMetricsObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
- JNIEnv* env = getenv(mVm);
-
- mObserverWeak = env->NewWeakGlobalRef(observer);
- LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
- "unable to create frame stats observer reference");
-
- jlongArray buffer = get_metrics_buffer(env, observer);
- jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(buffer));
- LOG_ALWAYS_FATAL_IF(bufferSize != kBufferSize,
- "Mismatched Java/Native FrameMetrics data format.");
-
- jobject messageQueueLocal = env->GetObjectField(
- observer, gFrameMetricsObserverClassInfo.messageQueue);
- mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
- LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
-
- mMessageHandler = new NotifyHandler(mVm, this);
- LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
- "OOM: unable to allocate NotifyHandler");
-}
-
-FrameMetricsObserverProxy::~FrameMetricsObserverProxy() {
- JNIEnv* env = getenv(mVm);
- env->DeleteWeakGlobalRef(mObserverWeak);
-}
-
-bool FrameMetricsObserverProxy::getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount) {
- FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
-
- if (elem.hasData.load()) {
- env->SetLongArrayRegion(sink, 0, kBufferSize, elem.buffer);
- *dropCount = elem.dropCount;
- mNextInQueue = (mNextInQueue + 1) % kRingSize;
- elem.hasData = false;
- return true;
- }
-
- return false;
-}
-
-void FrameMetricsObserverProxy::notify(const int64_t* stats) {
- FrameMetricsNotification& elem = mRingBuffer[mNextFree];
-
- if (!elem.hasData.load()) {
- memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
-
- elem.dropCount = mDroppedReports;
- mDroppedReports = 0;
-
- incStrong(nullptr);
- mNextFree = (mNextFree + 1) % kRingSize;
- elem.hasData = true;
-
- mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
- } else {
- mDroppedReports++;
- }
-}
-
-int register_android_view_FrameMetricsObserver(JNIEnv* env) {
- jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
- gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
- env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
- gFrameMetricsObserverClassInfo.messageQueue = GetFieldIDOrDie(
- env, observerClass, "mMessageQueue", "Landroid/os/MessageQueue;");
- gFrameMetricsObserverClassInfo.callback = GetMethodIDOrDie(
- env, observerClass, "notifyDataAvailable", "(I)V");
-
- jclass metricsClass = FindClassOrDie(env, "android/view/FrameMetrics");
- gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
- env, metricsClass, "mTimingData", "[J");
- return JNI_OK;
-}
-
-} // namespace android \ No newline at end of file
diff --git a/core/jni/android_view_FrameMetricsObserver.h b/core/jni/android_view_FrameMetricsObserver.h
deleted file mode 100644
index 647f51c4492d..000000000000
--- a/core/jni/android_view_FrameMetricsObserver.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.
- */
-
-#include "jni.h"
-#include "core_jni_helpers.h"
-
-#include "android_os_MessageQueue.h"
-
-#include <FrameInfo.h>
-#include <FrameMetricsObserver.h>
-
-namespace android {
-
-/*
- * Implements JNI layer for hwui frame metrics reporting.
- */
-class FrameMetricsObserverProxy : public uirenderer::FrameMetricsObserver {
-public:
- FrameMetricsObserverProxy(JavaVM *vm, jobject observer);
-
- ~FrameMetricsObserverProxy();
-
- jweak getObserverReference() {
- return mObserverWeak;
- }
-
- bool getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount);
-
- virtual void notify(const int64_t* stats);
-
-private:
- static const int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes);
- static constexpr int kRingSize = 3;
-
- class FrameMetricsNotification {
- public:
- FrameMetricsNotification() : hasData(false) {}
-
- std::atomic_bool hasData;
- int64_t buffer[kBufferSize];
- int dropCount = 0;
- };
-
- JavaVM* const mVm;
- jweak mObserverWeak;
-
- sp<MessageQueue> mMessageQueue;
- sp<MessageHandler> mMessageHandler;
- Message mMessage;
-
- int mNextFree = 0;
- int mNextInQueue = 0;
- FrameMetricsNotification mRingBuffer[kRingSize];
-
- int mDroppedReports = 0;
-};
-
-} // namespace android
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 170e467a72cd..69ca17c08257 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -30,7 +30,7 @@
#include <gui/BufferQueue.h>
#include <gui/Surface.h>
-#include "android_view_FrameMetricsObserver.h"
+#include "android_graphics_HardwareRendererObserver.h"
#include <private/EGL/cache.h>
@@ -580,28 +580,21 @@ static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
}
// ----------------------------------------------------------------------------
-// FrameMetricsObserver
+// HardwareRendererObserver
// ----------------------------------------------------------------------------
-static jlong android_view_ThreadedRenderer_addFrameMetricsObserver(JNIEnv* env,
- jclass clazz, jlong proxyPtr, jobject fso) {
- JavaVM* vm = nullptr;
- if (env->GetJavaVM(&vm) != JNI_OK) {
- LOG_ALWAYS_FATAL("Unable to get Java VM");
- return 0;
- }
-
+static void android_view_ThreadedRenderer_addObserver(JNIEnv* env, jclass clazz,
+ jlong proxyPtr, jlong observerPtr) {
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
- FrameMetricsObserver* observer = new FrameMetricsObserverProxy(vm, fso);
renderProxy->addFrameMetricsObserver(observer);
- return reinterpret_cast<jlong>(observer);
}
-static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env, jclass clazz,
+static void android_view_ThreadedRenderer_removeObserver(JNIEnv* env, jclass clazz,
jlong proxyPtr, jlong observerPtr) {
- FrameMetricsObserver* observer = reinterpret_cast<FrameMetricsObserver*>(observerPtr);
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
@@ -675,12 +668,8 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_setFrameCallback},
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
(void*)android_view_ThreadedRenderer_setFrameCompleteCallback },
- { "nAddFrameMetricsObserver",
- "(JLandroid/view/FrameMetricsObserver;)J",
- (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
- { "nRemoveFrameMetricsObserver",
- "(JJ)V",
- (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
+ { "nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver },
+ { "nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver },
{ "nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I",
(void*)android_view_ThreadedRenderer_copySurfaceInto },
{ "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c4ac89acd0db..df5b02c22a2d 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -31,6 +31,8 @@
// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
#include <sys/mount.h>
#include <linux/fs.h>
+#include <sys/types.h>
+#include <dirent.h>
#include <array>
#include <atomic>
@@ -40,6 +42,7 @@
#include <sstream>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <android/fdsan.h>
#include <arpa/inet.h>
@@ -51,6 +54,7 @@
#include <mntent.h>
#include <paths.h>
#include <signal.h>
+#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
@@ -159,6 +163,17 @@ static std::atomic_uint32_t gUsapPoolCount = 0;
*/
static int gUsapPoolEventFD = -1;
+static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
+
+/**
+ * Property to control if app data isolation is enabled.
+ */
+static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
+static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
+
/**
* The maximum value that the gUSAPPoolSizeMax variable may take. This value
* is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -663,7 +678,7 @@ static int UnmountTree(const char* path) {
return 0;
}
-static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+static void PrepareDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
fail_fn_t fail_fn) {
if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) {
fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s: %s",
@@ -671,6 +686,16 @@ static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
}
}
+static void PrepareDirIfNotPresent(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+ fail_fn_t fail_fn) {
+ struct stat sb;
+ if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) != -1) {
+ // Directory exists already
+ return;
+ }
+ PrepareDir(dir, mode, uid, gid, fail_fn);
+}
+
static void BindMount(const std::string& source_dir, const std::string& target_dir,
fail_fn_t fail_fn) {
if (TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr,
@@ -680,6 +705,15 @@ static void BindMount(const std::string& source_dir, const std::string& target_d
}
}
+static void MountAppDataTmpFs(const std::string& target_dir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount("tmpfs", target_dir.c_str(), "tmpfs",
+ MS_NOSUID | MS_NODEV | MS_NOEXEC, "uid=0,gid=0,mode=0751")) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount tmpfs to %s: %s",
+ target_dir.c_str(), strerror(errno)));
+ }
+}
+
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
static void MountEmulatedStorage(uid_t uid, jint mount_mode,
@@ -712,11 +746,19 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode,
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
bool isFuse = GetBoolProperty(kPropFuse, false);
- CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDir(user_source, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
if (isFuse) {
- BindMount(mount_mode == MOUNT_EXTERNAL_PASS_THROUGH ? pass_through_source : user_source,
- "/storage", fail_fn);
+ if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
+ MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) {
+ // For now, MediaProvider, installers and "full" get the pass_through mount
+ // view, which is currently identical to the sdcardfs write view.
+ //
+ // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER
+ BindMount(pass_through_source, "/storage", fail_fn);
+ } else {
+ BindMount(user_source, "/storage", fail_fn);
+ }
} else {
const std::string& storage_source = ExternalStorageViews[mount_mode];
BindMount(storage_source, "/storage", fail_fn);
@@ -1008,6 +1050,231 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
return pid;
}
+// Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it
+// from the actual app data directory in data mirror.
+static void createAndMountAppData(std::string_view package_name,
+ std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path,
+ std::string_view actual_data_path, fail_fn_t fail_fn) {
+
+ char mirrorAppDataPath[PATH_MAX];
+ char actualAppDataPath[PATH_MAX];
+ snprintf(mirrorAppDataPath, PATH_MAX, "%s/%s", mirror_data_path.data(),
+ mirror_pkg_dir_name.data());
+ snprintf(actualAppDataPath, PATH_MAX, "%s/%s", actual_data_path.data(), package_name.data());
+
+ PrepareDir(actualAppDataPath, 0700, AID_ROOT, AID_ROOT, fail_fn);
+
+ // Bind mount from original app data directory in mirror.
+ BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn);
+}
+
+// Get the directory name stored in /data/data. If device is unlocked it should be the same as
+// package name, otherwise it will be an encrypted name but with same inode number.
+static std::string getAppDataDirName(std::string_view parent_path, std::string_view package_name,
+ long long ce_data_inode, fail_fn_t fail_fn) {
+ // Check if directory exists
+ char tmpPath[PATH_MAX];
+ snprintf(tmpPath, PATH_MAX, "%s/%s", parent_path.data(), package_name.data());
+ struct stat s;
+ int err = stat(tmpPath, &s);
+ if (err == 0) {
+ // Directory exists, so return the directory name
+ return package_name.data();
+ } else {
+ if (errno != ENOENT) {
+ fail_fn(CREATE_ERROR("Unexpected error in getAppDataDirName: %s", strerror(errno)));
+ return nullptr;
+ }
+ // Directory doesn't exist, try to search the name from inode
+ DIR* dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == ce_data_inode) {
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+
+ // Fallback due to b/145989852, ce_data_inode stored in package manager may be corrupted
+ // if ino_t is 32 bits.
+ ino_t fixed_ce_data_inode = 0;
+ if ((ce_data_inode & UPPER_HALF_WORD_MASK) == UPPER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ce_data_inode & LOWER_HALF_WORD_MASK;
+ } else if ((ce_data_inode & LOWER_HALF_WORD_MASK) == LOWER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ((ce_data_inode >> 32) & LOWER_HALF_WORD_MASK);
+ }
+ if (fixed_ce_data_inode != 0) {
+ dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == fixed_ce_data_inode) {
+ long long d_ino = ent->d_ino;
+ ALOGW("Fallback success inode %lld -> %lld", ce_data_inode, d_ino);
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+ }
+ // Fallback done
+
+ fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(),
+ ce_data_inode, parent_path.data()));
+ return nullptr;
+ }
+}
+
+// Isolate app's data directory, by mounting a tmpfs on CE DE storage,
+// and create and bind mount app data in related_packages.
+static void isolateAppDataPerPackage(int userId, std::string_view package_name,
+ std::string_view volume_uuid, long long ce_data_inode, std::string_view actualCePath,
+ std::string_view actualDePath, fail_fn_t fail_fn) {
+
+ char mirrorCePath[PATH_MAX];
+ char mirrorDePath[PATH_MAX];
+ char mirrorCeParent[PATH_MAX];
+ snprintf(mirrorCeParent, PATH_MAX, "/data_mirror/data_ce/%s", volume_uuid.data());
+ snprintf(mirrorCePath, PATH_MAX, "%s/%d", mirrorCeParent, userId);
+ snprintf(mirrorDePath, PATH_MAX, "/data_mirror/data_de/%s/%d", volume_uuid.data(), userId);
+
+ createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn);
+
+ std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
+ createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
+}
+
+/**
+ * Make other apps data directory not visible in CE, DE storage.
+ *
+ * Apps without app data isolation can detect if another app is installed on system,
+ * by "touching" other apps data directory like /data/data/com.whatsapp, if it returns
+ * "Permission denied" it means apps installed, otherwise it returns "File not found".
+ * Traditional file permissions or SELinux can only block accessing those directories but
+ * can't fix fingerprinting like this.
+ * We fix it by "overlaying" data directory, and only relevant app data packages exists
+ * in data directories.
+ *
+ * Steps:
+ * 1). Collect a list of all related apps (apps with same uid and whitelisted apps) data info
+ * (package name, data stored volume uuid, and inode number of its CE data directory)
+ * 2). Mount tmpfs on /data/data, /data/user(_de) and /mnt/expand, so apps no longer
+ * able to access apps data directly.
+ * 3). For each related app, create its app data directory and bind mount the actual content
+ * from apps data mirror directory. This works on both CE and DE storage, as DE storage
+ * is always available even storage is FBE locked, while we use inode number to find
+ * the encrypted DE directory in mirror so we can still bind mount it successfully.
+ *
+ * Example:
+ * 0). Assuming com.android.foo CE data is stored in /data/data and no shared uid
+ * 1). Mount a tmpfs on /data/data, /data/user, /data/user_de, /mnt/expand
+ * List = ["com.android.foo", "null" (volume uuid "null"=default),
+ * 123456 (inode number)]
+ * 2). On DE storage, we create a directory /data/user_de/0/com.com.android.foo, and bind
+ * mount (in the app's mount namespace) it from /data_mirror/data_de/0/com.android.foo.
+ * 3). We do similar for CE storage. But in direct boot mode, as /data_mirror/data_ce/0/ is
+ * encrypted, we can't find a directory with name com.android.foo on it, so we will
+ * use the inode number to find the right directory instead, which that directory content will
+ * be decrypted after storage is decrypted.
+ *
+ */
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+ uid_t uid, const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+
+ const userid_t userId = multiuser_get_user_id(uid);
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+ int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+ // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+ if ((size % 3) != 0) {
+ fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
+ }
+
+ // Mount tmpfs on all possible data directories, so app no longer see the original apps data.
+ char internalCePath[PATH_MAX];
+ char internalLegacyCePath[PATH_MAX];
+ char internalDePath[PATH_MAX];
+ char externalPrivateMountPath[PATH_MAX];
+
+ snprintf(internalCePath, PATH_MAX, "/data/user");
+ snprintf(internalLegacyCePath, PATH_MAX, "/data/data");
+ snprintf(internalDePath, PATH_MAX, "/data/user_de");
+ snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+
+ MountAppDataTmpFs(internalLegacyCePath, fail_fn);
+ MountAppDataTmpFs(internalCePath, fail_fn);
+ MountAppDataTmpFs(internalDePath, fail_fn);
+ MountAppDataTmpFs(externalPrivateMountPath, fail_fn);
+
+ for (int i = 0; i < size; i += 3) {
+ jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
+ std::string packageName = extract_fn(package_str).value();
+
+ jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
+ std::string volUuid = extract_fn(vol_str).value();
+
+ jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
+ std::string inode = extract_fn(inode_str).value();
+ std::string::size_type sz;
+ long long ceDataInode = std::stoll(inode, &sz);
+
+ std::string actualCePath, actualDePath;
+ if (volUuid.compare("null") != 0) {
+ // Volume that is stored in /mnt/expand
+ char volPath[PATH_MAX];
+ char volCePath[PATH_MAX];
+ char volDePath[PATH_MAX];
+ char volCeUserPath[PATH_MAX];
+ char volDeUserPath[PATH_MAX];
+
+ snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str());
+ snprintf(volCePath, PATH_MAX, "%s/user", volPath);
+ snprintf(volDePath, PATH_MAX, "%s/user_de", volPath);
+ snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId);
+ snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId);
+
+ PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ actualCePath = volCeUserPath;
+ actualDePath = volDeUserPath;
+ } else {
+ // Internal volume that stored in /data
+ char internalCeUserPath[PATH_MAX];
+ char internalDeUserPath[PATH_MAX];
+ snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId);
+ snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId);
+ // If it's user 0, create a symlink /data/user/0 -> /data/data,
+ // otherwise create /data/user/$USER
+ if (userId == 0) {
+ symlink(internalLegacyCePath, internalCeUserPath);
+ actualCePath = internalLegacyCePath;
+ } else {
+ PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualCePath = internalCeUserPath;
+ }
+ PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualDePath = internalDeUserPath;
+ }
+ isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
+ actualCePath, actualDePath, fail_fn);
+ }
+}
+
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
@@ -1015,7 +1282,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint mount_external, jstring managed_se_info,
jstring managed_nice_name, bool is_system_server,
bool is_child_zygote, jstring managed_instruction_set,
- jstring managed_app_data_dir, bool is_top_app) {
+ jstring managed_app_data_dir, bool is_top_app,
+ jobjectArray pkg_data_info_list) {
const char* process_name = is_system_server ? "system_server" : "zygote";
auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1051,6 +1319,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
+ // System services, isolated process, webview/app zygote, old target sdk app, should
+ // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
+ // Isolated process / webview / app zygote should be gated by SELinux and file permission
+ // so they can't even traverse CE / DE directories.
+ if (pkg_data_info_list != nullptr
+ && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
+ fail_fn);
+ }
+
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
@@ -1417,7 +1695,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
- jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -1449,7 +1728,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
return pid;
}
@@ -1473,10 +1752,13 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
fds_to_ignore,
true);
if (pid == 0) {
+ // System server prcoess does not need data isolation so no need to
+ // know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
- false, nullptr, nullptr, /* is_top_app= */ false);
+ false, nullptr, nullptr, /* is_top_app= */ false,
+ /* pkg_data_info_list */ nullptr);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -1599,14 +1881,15 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
/**
@@ -1767,7 +2050,7 @@ static void com_android_internal_os_Zygote_nativeBoostUsapPriority(JNIEnv* env,
static const JNINativeMethod gMethods[] = {
{ "nativeForkAndSpecialize",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
(void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
{ "nativeForkSystemServer", "(II[II[[IJJ)I",
(void *) com_android_internal_os_Zygote_nativeForkSystemServer },
@@ -1780,7 +2063,7 @@ static const JNINativeMethod gMethods[] = {
{ "nativeForkUsap", "(II[IZ)I",
(void *) com_android_internal_os_Zygote_nativeForkUsap },
{ "nativeSpecializeAppProcess",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
(void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
{ "nativeInitNativeState", "(Z)V",
(void *) com_android_internal_os_Zygote_nativeInitNativeState },
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index bef0ee48afc0..8c1ecae67401 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,6 +33,7 @@
// Static whitelist of open paths that the zygote is allowed to keep open.
static const char* kPathWhitelist[] = {
+ "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
"/apex/com.android.conscrypt/javalib/conscrypt.jar",
"/apex/com.android.ipsec/javalib/ike.jar",
"/apex/com.android.media/javalib/updatable-media.jar",
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 0c2129ffa625..44581af10b95 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2472,4 +2472,36 @@ enum PageId {
// CATEGORY: SETTINGS
// OS: R
SETTINGS_BUGREPORT_HANDLER = 1808;
+
+ // Panel for adding Wi-Fi networks
+ // CATEGORY: SETTINGS
+ // OS: R
+ PANEL_ADD_WIFI_NETWORKS = 1809;
+
+ // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_TOGGLE_SCREEN_ACCESSIBILITY_BUTTON = 1810;
+
+ // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog in
+ // gesture mode
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_TOGGLE_SCREEN_GESTURE_NAVIGATION = 1811;
+
+ // OPEN: Settings > Accessibility > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT = 1812;
+
+ // OPEN: Settings > Accessibility > Magnification > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_MAGNIFICATION_EDIT_SHORTCUT = 1813;
+
+ // OPEN: Settings > Accessibility > Color correction > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_DALTONIZER_EDIT_SHORTCUT = 1814;
+
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4ea574dadc73..6a1ec6c849c8 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -219,6 +219,12 @@ message SecureSettingsProto {
}
optional Gesture gesture = 74;
+ message GestureNavigation {
+ optional SettingProto back_gesture_inset_scale_left = 1 [(android.privacy).dest = DEST_AUTOMATIC];
+ optional SettingProto back_gesture_inset_scale_right = 2 [(android.privacy).dest = DEST_AUTOMATIC];
+ }
+ optional GestureNavigation gesture_navigation = 77;
+
optional SettingProto immersive_mode_confirmations = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Incall {
@@ -316,6 +322,7 @@ message SecureSettingsProto {
optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message NfcPayment {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -564,5 +571,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 77;
+ // Next tag = 78;
}
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index f648912d36eb..5963f6a7f938 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -56,6 +56,7 @@ enum Root {
ROOT_VIDEOS = 9;
ROOT_MTP = 10;
ROOT_THIRD_PARTY_APP = 11;
+ ROOT_DOCUMENTS = 12;
}
enum ContextScope {
diff --git a/core/proto/android/util/quotatracker.proto b/core/proto/android/util/quotatracker.proto
new file mode 100644
index 000000000000..0dea853b0986
--- /dev/null
+++ b/core/proto/android/util/quotatracker.proto
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.util.quota;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+// A com.android.util.quota.QuotaTracker object.
+message QuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional bool is_enabled = 1;
+
+ // If quota is free for everything in the tracker.
+ optional bool is_global_quota_free = 2;
+
+ // Current elapsed realtime.
+ optional int64 elapsed_realtime = 3;
+
+ message AlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Whether the listener is waiting for an alarm or not.
+ optional bool is_waiting = 1;
+ // The time at which the alarm should go off, in the elapsed realtime timebase. Only
+ // valid if is_waiting is true.
+ optional int64 trigger_time_elapsed = 2;
+ }
+
+ // Next tag: 4
+}
+
+// A com.android.util.quota.Category object.
+message CategoryProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Name of the category set by the system service.
+ optional string name = 1;
+}
+
+// A com.android.util.quota.Uptc object.
+message UptcProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // UserHandle value. Should be 0, 10, 11, 12, etc. where 0 is the owner.
+ optional int32 user_id = 1;
+ // Package name
+ optional string name = 2;
+ // Tag set by the system service to differentiate calls.
+ optional string tag = 3;
+}
+
+// A com.android.util.quota.CountQuotaTracker object.
+message CountQuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional QuotaTrackerProto base_quota_data = 1;
+
+ message CountLimit {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional CategoryProto category = 1;
+ optional int32 limit = 2;
+ optional int64 window_size_ms = 3;
+ }
+ repeated CountLimit count_limit = 2;
+
+ message Event {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time the event occurred, in the elapsed realtime timebase.
+ optional int64 timestamp_elapsed = 1;
+ }
+
+ message ExecutionStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time after which this record should be considered invalid (out of date), in the
+ // elapsed realtime timebase.
+ optional int64 expiration_time_elapsed = 1;
+
+ optional int64 window_size_ms = 2;
+ optional int32 count_limit = 3;
+
+ // The total number of events that occurred in the window.
+ optional int32 count_in_window = 4;
+
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // count_in_window >= count_limit.
+ optional int64 in_quota_time_elapsed = 5;
+ }
+
+ message UptcStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+
+ // True if the UPTC has been given free quota.
+ optional bool is_quota_free = 2;
+
+ repeated Event events = 3;
+
+ repeated ExecutionStats execution_stats = 4;
+
+ optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 5;
+ }
+ repeated UptcStats uptc_stats = 3;
+
+ // Next tag: 4
+}
+
+// A com.android.util.quota.DurationQuotaTracker object.
+message DurationQuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional QuotaTrackerProto base_quota_data = 1;
+
+ message DurationLimit {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional CategoryProto category = 1;
+ optional int64 limit_ms = 2;
+ optional int64 window_size_ms = 3;
+ }
+ repeated DurationLimit duration_limit = 2;
+
+ message ExecutionStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time after which this record should be considered invalid (out of date), in the
+ // elapsed realtime timebase.
+ optional int64 expiration_time_elapsed = 1;
+
+ optional int32 window_size_ms = 2;
+ optional int64 duration_limit_ms = 3;
+
+ // The overall session duration in the window.
+ optional int64 session_duration_in_window_ms = 4;
+ // The number of individual long-running events in the window.
+ optional int32 event_count_in_window = 5;
+
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // session_duration_in_window_ms >= duration_limit_ms.
+ optional int64 in_quota_time_elapsed = 6;
+ }
+
+ message Timer {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // True if the Timer is actively tracking long-running events.
+ optional bool is_active = 1;
+ // The time this timer last became active. Only valid if is_active is true.
+ optional int64 start_time_elapsed = 2;
+ // How many long-running events are currently running. Valid only if is_active is true.
+ optional int32 event_count = 3;
+ }
+
+ message TimingSession {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 start_time_elapsed = 1;
+ optional int64 end_time_elapsed = 2;
+ // How many events started during this session. This only count long-running events, not
+ // instantaneous events.
+ optional int32 event_count = 3;
+ }
+
+ message UptcStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+
+ // True if the UPTC has been given free quota.
+ optional bool is_quota_free = 2;
+
+ optional Timer timer = 3;
+
+ repeated TimingSession saved_sessions = 4;
+
+ repeated ExecutionStats execution_stats = 5;
+
+ optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 6;
+ }
+ repeated UptcStats uptc_stats = 3;
+
+ message ReachedQuotaAlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 trigger_time_elapsed = 1;
+
+ message UptcTimes {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+ optional int64 out_of_quota_time_elapsed = 2;
+ }
+ repeated UptcTimes uptc_times = 2;
+ }
+ optional ReachedQuotaAlarmListener reached_quota_alarm_listener = 4;
+
+ // Next tag: 5
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c925744fea3b..dd09427d64ed 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -637,7 +637,7 @@
<!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be
removed. -->
- <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIME" />
+ <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
<!-- For tether entitlement recheck-->
<protected-broadcast
@@ -1850,6 +1850,13 @@
android:description="@string/permdesc_vibrate"
android:protectionLevel="normal|instant" />
+ <!-- Allows access to the vibrator always-on settings.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
+ android:protectionLevel="signature" />
+
<!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
from dimming.
<p>Protection level: normal
@@ -4410,6 +4417,12 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
+ <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
+ implementations which do not support running both concurrently.
+ @hide -->
+ <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by system/priv apps implementing sound trigger detection services
@hide
@SystemApi -->
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 9c725b93eec6..4d7846dfb9cc 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -45,7 +45,8 @@
android:ellipsize="end"
android:fontFamily="@android:string/config_headlineFontFamily"
android:textColor="?android:attr/textColorPrimary"
- android:maxLines="2"/>
+ android:maxLines="2"
+ android:focusable="true"/>
<LinearLayout
android:id="@+id/copy_button"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e7c34151af4b..85406c7e7cec 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2357,15 +2357,22 @@
<!-- Whether to only install system packages on a user if they're whitelisted for that user
type. These are flags and can be freely combined.
- 0 (0b000) - disable whitelist (install all system packages; no logging)
- 1 (0b001) - enforce (only install system packages if they are whitelisted)
- 2 (0b010) - log (log when a non-whitelisted package is run)
- 4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted
- 8 (0b1000) - ignore OTAs (don't install system packages during OTAs)
+ 0 - disable whitelist (install all system packages; no logging)
+ 1 - enforce (only install system packages if they are whitelisted)
+ 2 - log (log when a non-whitelisted package is run)
+ 4 - any package not mentioned in the whitelist file is implicitly whitelisted on all users
+ 8 - same as 4, but just for the SYSTEM user
+ 16 - ignore OTAs (don't install system packages during OTAs)
+ Common scenarios:
+ - to enable feature (fully enforced) for a complete whitelist: 1
+ - to enable feature for an incomplete whitelist (so use implicit whitelist mode): 5
+ - to enable feature but implicitly whitelist for SYSTEM user to ease local development: 9
+ - to disable feature completely if it had never been enabled: 16
+ - to henceforth disable feature and try to undo its previous effects: 0
Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in
frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
- <integer name="config_userTypePackageWhitelistMode">13</integer> <!-- 0b1101 -->
- <!-- TODO(b/143200798): Change to value 5, i.e. 0b0101, when b/143200798 is resolved. -->
+ <integer name="config_userTypePackageWhitelistMode">29</integer> <!-- 1+4+8+16 -->
+ <!-- TODO(b/143200798): Change to value 13, i.e. 1+4+8, when b/143200798 is resolved. -->
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
@@ -2907,10 +2914,6 @@
<!-- Whether to use voip audio mode for ims call -->
<bool name="config_use_voip_mode_for_ims">false</bool>
- <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
- empty string is passed in -->
- <string name="config_ims_package"/>
-
<!-- String array containing numbers that shouldn't be logged. Country-specific. -->
<string-array name="unloggable_phone_numbers" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 9791241a52af..2543967be8a8 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -772,4 +772,8 @@
<dimen name="resolver_small_margin">18dp</dimen>
<dimen name="resolver_edge_margin">24dp</dimen>
<dimen name="resolver_elevation">1dp</dimen>
+
+ <!-- Assistant handles -->
+ <dimen name="assist_handle_shadow_radius">2dp</dimen>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1631e0f00f07..1e59c37c7577 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -295,7 +295,6 @@
<java-symbol type="bool" name="config_enableBurnInProtection" />
<java-symbol type="bool" name="config_hotswapCapable" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
- <java-symbol type="string" name="config_ims_package" />
<java-symbol type="string" name="config_wwan_network_service_package" />
<java-symbol type="string" name="config_wlan_network_service_package" />
<java-symbol type="string" name="config_wwan_network_service_class" />
@@ -3791,4 +3790,9 @@
<!-- For contacts provider. -->
<java-symbol type="string" name="config_rawContactsLocalAccountName" />
<java-symbol type="string" name="config_rawContactsLocalAccountType" />
+
+ <!-- Assistant handles -->
+ <java-symbol type="dimen" name="assist_handle_shadow_radius" />
+
+
</resources>
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index bbd442d53f76..6c5d5485664e 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -16,7 +16,11 @@
package android.app.activity;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.content.res.Configuration;
@@ -110,7 +114,123 @@ public class ActivityManagerTest extends AndroidTestCase {
assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD);
}
}
-
+
+ @SmallTest
+ public void testTaskDescriptionCopyFrom() {
+ TaskDescription td1 = new TaskDescription(
+ "test label", // label
+ null, // bitmap
+ 21, // iconRes
+ "dummy file", // iconFilename
+ 0x111111, // colorPrimary
+ 0x222222, // colorBackground
+ 0x333333, // statusBarColor
+ 0x444444, // navigationBarColor
+ true, // ensureStatusBarContrastWhenTransparent
+ true, // ensureNavigationBarContrastWhenTransparent
+ RESIZE_MODE_RESIZEABLE, // resizeMode
+ 10, // minWidth
+ 20 // minHeight
+ );
+
+ TaskDescription td2 = new TaskDescription();
+ // Must overwrite all the fields
+ td2.copyFrom(td1);
+
+ assertEquals(td1.getLabel(), td2.getLabel());
+ assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
+ assertEquals(td1.getIconFilename(), td2.getIconFilename());
+ assertEquals(td1.getIconResource(), td2.getIconResource());
+ assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
+ assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+ assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+ assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
+ td2.getEnsureStatusBarContrastWhenTransparent());
+ assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
+ td2.getEnsureNavigationBarContrastWhenTransparent());
+ assertEquals(td1.getResizeMode(), td2.getResizeMode());
+ assertEquals(td1.getMinWidth(), td2.getMinWidth());
+ assertEquals(td1.getMinHeight(), td2.getMinHeight());
+ }
+
+ @SmallTest
+ public void testTaskDescriptionCopyFromPreserveHiddenFields() {
+ TaskDescription td1 = new TaskDescription(
+ "test label", // label
+ null, // bitmap
+ 21, // iconRes
+ "dummy file", // iconFilename
+ 0x111111, // colorPrimary
+ 0x222222, // colorBackground
+ 0x333333, // statusBarColor
+ 0x444444, // navigationBarColor
+ false, // ensureStatusBarContrastWhenTransparent
+ false, // ensureNavigationBarContrastWhenTransparent
+ RESIZE_MODE_UNRESIZEABLE, // resizeMode
+ 10, // minWidth
+ 20 // minHeight
+ );
+
+ TaskDescription td2 = new TaskDescription(
+ "test label2", // label
+ null, // bitmap
+ 212, // iconRes
+ "dummy file2", // iconFilename
+ 0x1111112, // colorPrimary
+ 0x2222222, // colorBackground
+ 0x3333332, // statusBarColor
+ 0x4444442, // navigationBarColor
+ true, // ensureStatusBarContrastWhenTransparent
+ true, // ensureNavigationBarContrastWhenTransparent
+ RESIZE_MODE_RESIZEABLE, // resizeMode
+ 102, // minWidth
+ 202 // minHeight
+ );
+
+ // Must overwrite all public and hidden fields, since other has all fields set.
+ td2.copyFromPreserveHiddenFields(td1);
+
+ assertEquals(td1.getLabel(), td2.getLabel());
+ assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
+ assertEquals(td1.getIconFilename(), td2.getIconFilename());
+ assertEquals(td1.getIconResource(), td2.getIconResource());
+ assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
+ assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+ assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+ assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
+ td2.getEnsureStatusBarContrastWhenTransparent());
+ assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
+ td2.getEnsureNavigationBarContrastWhenTransparent());
+ assertEquals(td1.getResizeMode(), td2.getResizeMode());
+ assertEquals(td1.getMinWidth(), td2.getMinWidth());
+ assertEquals(td1.getMinHeight(), td2.getMinHeight());
+
+ TaskDescription td3 = new TaskDescription();
+ // Must overwrite only public fields, and preserve hidden fields.
+ td2.copyFromPreserveHiddenFields(td3);
+
+ // Overwritten fields
+ assertEquals(td3.getLabel(), td2.getLabel());
+ assertEquals(td3.getInMemoryIcon(), td2.getInMemoryIcon());
+ assertEquals(td3.getIconFilename(), td2.getIconFilename());
+ assertEquals(td3.getIconResource(), td2.getIconResource());
+ assertEquals(td3.getPrimaryColor(), td2.getPrimaryColor());
+ assertEquals(td3.getEnsureStatusBarContrastWhenTransparent(),
+ td2.getEnsureStatusBarContrastWhenTransparent());
+ assertEquals(td3.getEnsureNavigationBarContrastWhenTransparent(),
+ td2.getEnsureNavigationBarContrastWhenTransparent());
+
+ // Preserved fields
+ assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+ assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+ assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getResizeMode(), td2.getResizeMode());
+ assertEquals(td1.getMinWidth(), td2.getMinWidth());
+ assertEquals(td1.getMinHeight(), td2.getMinHeight());
+ }
+
// If any entries in appear in the list, sanity check them against all running applications
private void checkErrorListSanity(List<ActivityManager.ProcessErrorStateInfo> errList) {
if (errList == null) return;
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index cce38f6756fc..68d95cd38497 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -116,7 +116,8 @@ public class InsetsAnimationControlImplTest {
consumers.put(ITYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- () -> mMockTransactionApplier, mMockController, 10 /* durationMs */);
+ () -> mMockTransactionApplier, mMockController, 10 /* durationMs */,
+ false /* fade */);
}
@Test
@@ -133,6 +134,7 @@ public class InsetsAnimationControlImplTest {
0f /* fraction */);
mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+ assertEquals(1f, mController.getCurrentAlpha(), 1f - mController.getCurrentAlpha());
ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
verify(mMockTransactionApplier).scheduleApply(captor.capture());
@@ -147,6 +149,23 @@ public class InsetsAnimationControlImplTest {
}
@Test
+ public void testChangeAlphaNoInsets() {
+ Insets initialInsets = mController.getCurrentInsets();
+ mController.setInsetsAndAlpha(null, 0.5f, 0f /* fraction*/);
+ mController.applyChangeInsets(new InsetsState());
+ assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+ assertEquals(initialInsets, mController.getCurrentInsets());
+ }
+
+ @Test
+ public void testChangeInsetsAndAlpha() {
+ mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 0.5f, 1f);
+ mController.applyChangeInsets(new InsetsState());
+ assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+ assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+ }
+
+ @Test
public void testFinishing() {
when(mMockController.getState()).thenReturn(mInsetsState);
mController.finish(true /* shown */);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 80098c5a81f5..0574775712a6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -157,6 +157,7 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+ <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e07edd414c18..624fc50030a0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -240,6 +240,13 @@ applications that come with the platform
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.tethering">
+ <permission name="android.permission.MANAGE_USB"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+ <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.server.telecom">
<permission name="android.permission.BIND_CONNECTION_SERVICE"/>
<permission name="android.permission.BIND_INCALL_SERVICE"/>
@@ -343,6 +350,8 @@ applications that come with the platform
<permission name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
<!-- Permission required for Telecom car mode CTS tests. -->
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <!-- Permission required for Tethering CTS tests. -->
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 3f3ad578e8d7..3b864139cf71 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,7 +28,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.TimeUtils;
-import android.view.FrameMetricsObserver;
import android.view.IGraphicsStats;
import android.view.IGraphicsStatsCallback;
import android.view.NativeVectorDrawableAnimator;
@@ -38,8 +37,6 @@ import android.view.SurfaceHolder;
import android.view.TextureLayer;
import android.view.animation.AnimationUtils;
-import com.android.internal.util.VirtualRefBasePtr;
-
import java.io.File;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -598,9 +595,8 @@ public class HardwareRenderer {
*
* @hide
*/
- public void addFrameMetricsObserver(FrameMetricsObserver observer) {
- long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
- observer.mNative = new VirtualRefBasePtr(nativeObserver);
+ public void addObserver(HardwareRendererObserver observer) {
+ nAddObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -608,9 +604,8 @@ public class HardwareRenderer {
*
* @hide
*/
- public void removeFrameMetricsObserver(FrameMetricsObserver observer) {
- nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
- observer.mNative = null;
+ public void removeObserver(HardwareRendererObserver observer) {
+ nRemoveObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -1170,10 +1165,9 @@ public class HardwareRenderer {
private static native void nSetFrameCompleteCallback(long nativeProxy,
FrameCompleteCallback callback);
- private static native long nAddFrameMetricsObserver(long nativeProxy,
- FrameMetricsObserver observer);
+ private static native void nAddObserver(long nativeProxy, long nativeObserver);
- private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
+ private static native void nRemoveObserver(long nativeProxy, long nativeObserver);
private static native int nCopySurfaceInto(Surface surface,
int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java
new file mode 100644
index 000000000000..da9d03c639f7
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareRendererObserver.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.graphics;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+/**
+ * Provides streaming access to frame stats information from HardwareRenderer to apps.
+ *
+ * @hide
+ */
+public class HardwareRendererObserver {
+ private final long[] mFrameMetrics;
+ private final Handler mHandler;
+ private final OnFrameMetricsAvailableListener mListener;
+ private VirtualRefBasePtr mNativePtr;
+
+ /**
+ * Interface for clients that want frame timing information for each frame rendered.
+ * @hide
+ */
+ public interface OnFrameMetricsAvailableListener {
+ /**
+ * Called when information is available for the previously rendered frame.
+ *
+ * Reports can be dropped if this callback takes too long to execute, as the report producer
+ * cannot wait for the consumer to complete.
+ *
+ * It is highly recommended that clients copy the metrics array within this method
+ * and defer additional computation or storage to another thread to avoid unnecessarily
+ * dropping reports.
+ *
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * this callback was invoked.
+ */
+ void onFrameMetricsAvailable(int dropCountSinceLastInvocation);
+ }
+
+ /**
+ * Creates a FrameMetricsObserver
+ *
+ * @param frameMetrics the available metrics. This array is reused on every call to the listener
+ * and thus <strong>this reference should only be used within the scope of the listener callback
+ * as data is not guaranteed to be valid outside the scope of that method</strong>.
+ * @param handler the Handler to use when invoking callbacks
+ */
+ public HardwareRendererObserver(@NonNull OnFrameMetricsAvailableListener listener,
+ @NonNull long[] frameMetrics, @NonNull Handler handler) {
+ if (handler == null || handler.getLooper() == null) {
+ throw new NullPointerException("handler and its looper cannot be null");
+ }
+
+ if (handler.getLooper().getQueue() == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameMetrics = frameMetrics;
+ mHandler = handler;
+ mListener = listener;
+ mNativePtr = new VirtualRefBasePtr(nCreateObserver());
+ }
+
+ /*package*/ long getNativeInstance() {
+ return mNativePtr.get();
+ }
+
+ // Called by native on the provided Handler
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable() {
+ mHandler.post(() -> {
+ boolean hasMoreData = true;
+ while (hasMoreData) {
+ // a drop count of -1 is a sentinel that no more buffers are available
+ int dropCount = nGetNextBuffer(mNativePtr.get(), mFrameMetrics);
+ if (dropCount >= 0) {
+ mListener.onFrameMetricsAvailable(dropCount);
+ } else {
+ hasMoreData = false;
+ }
+ }
+ });
+ }
+
+ private native long nCreateObserver();
+ private static native int nGetNextBuffer(long nativePtr, long[] data);
+}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 8ebac667aca3..aecef8ee3413 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -270,7 +270,7 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
AssetFileDescriptor assetFd = null;
try {
- if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
+ if (mUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
"image/*", null);
} else {
diff --git a/media/Android.bp b/media/Android.bp
index e2bdad1f21b9..75ccb227e5c5 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -77,20 +77,13 @@ filegroup {
path: "apex/java"
}
-metalava_updatable_media_args = " --error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide MissingPermission --hide BroadcastBehavior " +
- "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
- "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
- "--hide HiddenTypedefConstant --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) "
-
droidstubs {
name: "updatable-media-stubs",
srcs: [
":updatable-media-srcs",
":framework-media-annotation-srcs",
],
- args: metalava_updatable_media_args,
+ defaults: [ "framework-module-stubs-defaults-systemapi" ],
aidl: {
// TODO(b/135922046) remove this
include_dirs: ["frameworks/base/core/java"],
@@ -109,3 +102,67 @@ java_library {
srcs: [":framework-media-annotation-srcs"],
installable: false,
}
+
+aidl_interface {
+ name: "audio_common-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/audio/common/AudioChannelMask.aidl",
+ "java/android/media/audio/common/AudioConfig.aidl",
+ "java/android/media/audio/common/AudioFormat.aidl",
+ "java/android/media/audio/common/AudioOffloadInfo.aidl",
+ "java/android/media/audio/common/AudioStreamType.aidl",
+ "java/android/media/audio/common/AudioUsage.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "soundtrigger_middleware-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameter.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
+ "java/android/media/soundtrigger_middleware/Phrase.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionMode.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModelType.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
+ "java/android/media/soundtrigger_middleware/Status.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ ndk: {
+ // Not currently needed, and disabled because of b/146172425
+ enabled: false,
+ },
+ },
+ imports: [ "audio_common-aidl" ],
+}
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
new file mode 100644
index 000000000000..88a829546989
--- /dev/null
+++ b/media/java/android/media/MediaMetrics.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Bundle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MediaMetrics is the Java interface to the MediaMetrics service.
+ *
+ * This is used to collect media statistics by the framework.
+ * It is not intended for direct application use.
+ *
+ * @hide
+ */
+public class MediaMetrics {
+ public static final String TAG = "MediaMetrics";
+
+ /**
+ * The TYPE constants below should match those in native MediaMetricsItem.h
+ */
+ private static final int TYPE_NONE = 0;
+ private static final int TYPE_INT32 = 1; // Java integer
+ private static final int TYPE_INT64 = 2; // Java long
+ private static final int TYPE_DOUBLE = 3; // Java double
+ private static final int TYPE_CSTRING = 4; // Java string
+ private static final int TYPE_RATE = 5; // Two longs, ignored in Java
+
+ // The charset used for encoding Strings to bytes.
+ private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
+
+ /**
+ * Item records properties and delivers to the MediaMetrics service
+ *
+ */
+ public static class Item {
+
+ /*
+ * MediaMetrics Item
+ *
+ * Creates a Byte String and sends to the MediaMetrics service.
+ * The Byte String serves as a compact form for logging data
+ * with low overhead for storage.
+ *
+ * The Byte String format is as follows:
+ *
+ * For Java
+ * int64 corresponds to long
+ * int32, uint32 corresponds to int
+ * uint16 corresponds to char
+ * uint8, int8 corresponds to byte
+ *
+ * For items transmitted from Java, uint8 and uint32 values are limited
+ * to INT8_MAX and INT32_MAX. This constrains the size of large items
+ * to 2GB, which is consistent with ByteBuffer max size. A native item
+ * can conceivably have size of 4GB.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is usually little endian.
+ *
+ * Note that primitive data (ints, doubles) within a Byte String has
+ * no extra padding or alignment requirements, like ByteBuffer.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key, a string which is 0 terminated (UTF-8).
+ * (int32) pid
+ * (int32) uid
+ * (int64) timestamp
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ * (uint16) property size, including property size field itself
+ * (uint8) type of property
+ * (int8)+ key string, including 0 termination
+ * based on type of property (given above), one of:
+ * (int32)
+ * (int64)
+ * (double)
+ * (int8)+ for TYPE_CSTRING, including 0 termination
+ * (int64, int64) for rate
+ * -- end body
+ * -- end of item
+ *
+ * To record a MediaMetrics event, one creates a new item with an id,
+ * then use a series of puts to add properties
+ * and then a record() to send to the MediaMetrics service.
+ *
+ * The properties may not be unique, and putting a later property with
+ * the same name as an earlier property will overwrite the value and type
+ * of the prior property.
+ *
+ * The timestamp can only be recorded by a system service (and is ignored otherwise;
+ * the MediaMetrics service will fill in the timestamp as needed).
+ *
+ * The units of time are in SystemClock.elapsedRealtimeNanos().
+ *
+ * A clear() may be called to reset the properties to empty, the time to 0, but keep
+ * the other entries the same. This may be called after record().
+ * Additional properties may be added after calling record(). Changing the same property
+ * repeatedly is discouraged as - for this particular implementation - extra data
+ * is stored per change.
+ *
+ * new MediaMetrics.Item(mSomeId)
+ * .putString("event", "javaCreate")
+ * .putInt("value", intValue)
+ * .record();
+ */
+
+ /**
+ * Creates an Item with server added uid, time.
+ *
+ * This is the typical way to record a MediaMetrics item.
+ *
+ * @param key the Metrics ID associated with the item.
+ */
+ public Item(String key) {
+ this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
+ 2048 /* capacity */);
+ }
+
+ /**
+ * Creates an Item specifying pid, uid, time, and initial Item capacity.
+ *
+ * This might be used by a service to specify a different PID or UID for a client.
+ *
+ * @param key the Metrics ID associated with the item.
+ * An app may only set properties on an item which has already been
+ * logged previously by a service.
+ * @param pid the process ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param uid the user ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param timeNs the time when the item occurred (may be in the past).
+ * A value of 0 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill it in.
+ * Should be obtained from SystemClock.elapsedRealtimeNanos().
+ * @param capacity the anticipated size to use for the buffer.
+ * If the capacity is too small, the buffer will be resized to accommodate.
+ * This is amortized to copy data no more than twice.
+ */
+ public Item(String key, int pid, int uid, long timeNs, int capacity) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE - 1) {
+ throw new IllegalArgumentException("Key length too large");
+ }
+
+ // Version 0 - compute the header offsets here.
+ mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
+ mPidOffset = mHeaderSize - 16;
+ mUidOffset = mHeaderSize - 12;
+ mTimeNsOffset = mHeaderSize - 8;
+ mPropertyCountOffset = mHeaderSize;
+ mPropertyStartOffset = mHeaderSize + 4;
+
+ mKey = key;
+ mBuffer = ByteBuffer.allocateDirect(
+ Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
+
+ // Version 0 - fill the ByteBuffer with the header (some details updated later).
+ mBuffer.order(ByteOrder.nativeOrder())
+ .putInt((int) 0) // total size in bytes (filled in later)
+ .putInt((int) mHeaderSize) // size of header
+ .putChar((char) FORMAT_VERSION) // version
+ .putChar((char) (keyLength + 1)) // length, with zero termination
+ .put(keyBytes).put((byte) 0)
+ .putInt(pid)
+ .putInt(uid)
+ .putLong(timeNs);
+ if (mHeaderSize != mBuffer.position()) {
+ throw new IllegalStateException("Mismatched sizing");
+ }
+ mBuffer.putInt(0); // number of properties (to be later filled in by record()).
+ }
+
+ /**
+ * Sets the property with key to an integer (32 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putInt(String key, int value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT32)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putInt(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a long (64 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putLong(String key, long value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT64)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putLong(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a double value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putDouble(String key, double value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_DOUBLE)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putDouble(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a String value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putString(String key, String value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_CSTRING)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .put(valueBytes).put((byte) 0); // value, zero term.
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the pid to the provided value.
+ *
+ * @param pid which can be -1 if the service is to fill it in from the calling info.
+ * @return itself
+ */
+ public Item setPid(int pid) {
+ mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the uid to the provided value.
+ *
+ * The UID represents the client associated with the property. This must be the UID
+ * of the application if it comes from the application client.
+ *
+ * Trusted services are allowed to set the uid for a client-related item.
+ *
+ * @param uid which can be -1 if the service is to fill it in from calling info.
+ * @return itself
+ */
+ public Item setUid(int uid) {
+ mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the timestamp to the provided value.
+ *
+ * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
+ * This should be associated with the occurrence of the event. It is recommended that
+ * the event be registered immediately when it occurs, and no later than 500ms
+ * (and certainly not in the future).
+ *
+ * @param timeNs which can be 0 if the service is to fill it in at the time of call.
+ * @return itself
+ */
+ public Item setTimestamp(long timeNs) {
+ mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
+ return this;
+ }
+
+ /**
+ * Clears the properties and resets the time to 0.
+ *
+ * No other values are changed.
+ *
+ * @return itself
+ */
+ public Item clear() {
+ mBuffer.position(mPropertyStartOffset);
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.putLong(mTimeNsOffset, 0); // reset time.
+ mPropertyCount = 0;
+ return this;
+ }
+
+ /**
+ * Sends the item to the MediaMetrics service.
+ *
+ * The item properties are unchanged, hence record() may be called more than once
+ * to send the same item twice. Also, record() may be called without any properties.
+ *
+ * @return true if successful.
+ */
+ public boolean record() {
+ updateHeader();
+ return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
+ }
+
+ /**
+ * Converts the Item to a Bundle.
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @return a Bundle with the keys set according to data in the Item's buffer.
+ */
+ @TestApi
+ public Bundle toBundle() {
+ updateHeader();
+
+ final ByteBuffer buffer = mBuffer.duplicate();
+ buffer.order(ByteOrder.nativeOrder()) // restore order property
+ .flip(); // convert from write buffer to read buffer
+
+ return toBundle(buffer);
+ }
+
+ // The following constants are used for tests to extract
+ // the content of the Bundle for CTS testing.
+ @TestApi
+ public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
+ @TestApi
+ public static final String BUNDLE_HEADER_SIZE = "_headerSize";
+ @TestApi
+ public static final String BUNDLE_VERSION = "_version";
+ @TestApi
+ public static final String BUNDLE_KEY_SIZE = "_keySize";
+ @TestApi
+ public static final String BUNDLE_KEY = "_key";
+ @TestApi
+ public static final String BUNDLE_PID = "_pid";
+ @TestApi
+ public static final String BUNDLE_UID = "_uid";
+ @TestApi
+ public static final String BUNDLE_TIMESTAMP = "_timestamp";
+ @TestApi
+ public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+ /**
+ * Converts a buffer contents to a bundle
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @param buffer contains the byte data serialized according to the byte string version.
+ * @return a Bundle with the keys set according to data in the buffer.
+ */
+ @TestApi
+ public static Bundle toBundle(ByteBuffer buffer) {
+ final Bundle bundle = new Bundle();
+
+ final int totalSize = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final char version = buffer.getChar();
+ final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
+
+ if (totalSize < 0 || headerSize < 0) {
+ throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
+ }
+ final String key;
+ if (keySize > 0) {
+ key = getStringFromBuffer(buffer, keySize);
+ } else {
+ throw new IllegalArgumentException("Illegal null key");
+ }
+
+ final int pid = buffer.getInt();
+ final int uid = buffer.getInt();
+ final long timestamp = buffer.getLong();
+
+ // Verify header size (depending on version).
+ final int headerRead = buffer.position();
+ if (version == 0) {
+ if (headerRead != headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " != headerSize:" + headerSize);
+ }
+ } else {
+ // future versions should only increase header size
+ // by adding to the end.
+ if (headerRead > headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " > headerSize:" + headerSize);
+ } else if (headerRead < headerSize) {
+ buffer.position(headerSize);
+ }
+ }
+
+ // Body always starts with properties.
+ final int propertyCount = buffer.getInt();
+ if (propertyCount < 0) {
+ throw new IllegalArgumentException(
+ "Cannot have more than " + Integer.MAX_VALUE + " properties");
+ }
+ bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
+ bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
+ bundle.putChar(BUNDLE_VERSION, version);
+ bundle.putChar(BUNDLE_KEY_SIZE, keySize);
+ bundle.putString(BUNDLE_KEY, key);
+ bundle.putInt(BUNDLE_PID, pid);
+ bundle.putInt(BUNDLE_UID, uid);
+ bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
+ bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
+
+ for (int i = 0; i < propertyCount; ++i) {
+ final int initialBufferPosition = buffer.position();
+ final char propSize = buffer.getChar();
+ final byte type = buffer.get();
+
+ // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
+ final String propKey = getStringFromBuffer(buffer);
+ switch (type) {
+ case TYPE_INT32:
+ bundle.putInt(propKey, buffer.getInt());
+ break;
+ case TYPE_INT64:
+ bundle.putLong(propKey, buffer.getLong());
+ break;
+ case TYPE_DOUBLE:
+ bundle.putDouble(propKey, buffer.getDouble());
+ break;
+ case TYPE_CSTRING:
+ bundle.putString(propKey, getStringFromBuffer(buffer));
+ break;
+ case TYPE_NONE:
+ break; // ignore on Java side
+ case TYPE_RATE:
+ buffer.getLong(); // consume the first int64_t of rate
+ buffer.getLong(); // consume the second int64_t of rate
+ break; // ignore on Java side
+ default:
+ // These are unsupported types for version 0
+ // We ignore them if the version is greater than 0.
+ if (version == 0) {
+ throw new IllegalArgumentException(
+ "Property " + propKey + " has unsupported type " + type);
+ }
+ buffer.position(initialBufferPosition + propSize); // advance and skip
+ break;
+ }
+ final int deltaPosition = buffer.position() - initialBufferPosition;
+ if (deltaPosition != propSize) {
+ throw new IllegalArgumentException("propSize:" + propSize
+ + " != deltaPosition:" + deltaPosition);
+ }
+ }
+
+ final int finalPosition = buffer.position();
+ if (finalPosition != totalSize) {
+ throw new IllegalArgumentException("totalSize:" + totalSize
+ + " != finalPosition:" + finalPosition);
+ }
+ return bundle;
+ }
+
+ // Version 0 byte offsets for the header.
+ private static final int FORMAT_VERSION = 0;
+ private static final int TOTAL_SIZE_OFFSET = 0;
+ private static final int HEADER_SIZE_OFFSET = 4;
+ private static final int MINIMUM_PAYLOAD_SIZE = 4;
+ private final int mPidOffset; // computed in constructor
+ private final int mUidOffset; // computed in constructor
+ private final int mTimeNsOffset; // computed in constructor
+ private final int mPropertyCountOffset; // computed in constructor
+ private final int mPropertyStartOffset; // computed in constructor
+ private final int mHeaderSize; // computed in constructor
+
+ private final String mKey;
+
+ private ByteBuffer mBuffer; // may be reallocated if capacity is insufficient.
+ private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
+
+ private int reserveProperty(byte[] keyBytes, int payloadSize) {
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE) {
+ throw new IllegalStateException("property key too long "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET));
+ }
+ if (payloadSize > Character.MAX_VALUE) {
+ throw new IllegalStateException("payload too large " + payloadSize);
+ }
+
+ // See the byte string property format above.
+ final int size = 2 /* length */
+ + 1 /* type */
+ + keyLength + 1 /* key length with zero termination */
+ + payloadSize; /* payload size */
+
+ if (size > Character.MAX_VALUE) {
+ throw new IllegalStateException("Item property "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
+ }
+
+ if (mBuffer.remaining() < size) {
+ int newCapacity = mBuffer.position() + size;
+ if (newCapacity > Integer.MAX_VALUE >> 1) {
+ throw new IllegalStateException(
+ "Item memory requirements too large: " + newCapacity);
+ }
+ newCapacity <<= 1;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
+ buffer.order(ByteOrder.nativeOrder());
+
+ // Copy data from old buffer to new buffer.
+ mBuffer.flip();
+ buffer.put(mBuffer);
+
+ // set buffer to new buffer
+ mBuffer = buffer;
+ }
+ return size;
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer) {
+ return getStringFromBuffer(buffer, Integer.MAX_VALUE);
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer, int size) {
+ int i = buffer.position();
+ int limit = buffer.limit();
+ if (size < Integer.MAX_VALUE - i && i + size < limit) {
+ limit = i + size;
+ }
+ for (; i < limit; ++i) {
+ if (buffer.get(i) == 0) {
+ final int newPosition = i + 1;
+ if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
+ throw new IllegalArgumentException("chars consumed at " + i + ": "
+ + (newPosition - buffer.position()) + " != size: " + size);
+ }
+ final String found;
+ if (buffer.hasArray()) {
+ found = new String(
+ buffer.array(), buffer.position() + buffer.arrayOffset(),
+ i - buffer.position(), MEDIAMETRICS_CHARSET);
+ buffer.position(newPosition);
+ } else {
+ final byte[] array = new byte[i - buffer.position()];
+ buffer.get(array);
+ found = new String(array, MEDIAMETRICS_CHARSET);
+ buffer.get(); // remove 0.
+ }
+ return found;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No zero termination found in string position: "
+ + buffer.position() + " end: " + i);
+ }
+
+ /**
+ * May be called multiple times - just makes the header consistent with the current
+ * properties written.
+ */
+ private void updateHeader() {
+ // Buffer sized properly in constructor.
+ mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position()) // set total length
+ .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
+ }
+ }
+
+ private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
+}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3e6f4c01873c..bad0ef4104fe 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -23,6 +23,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -51,7 +52,6 @@ import java.util.concurrent.Executor;
* @hide
*/
public class MediaRouter2 {
-
/** @hide */
@Retention(SOURCE)
@IntDef(value = {
@@ -102,13 +102,11 @@ public class MediaRouter2 {
new CopyOnWriteArrayList<>();
private final String mPackageName;
+ @GuardedBy("sLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- //TODO: Use a lock for this to cover the below use case
- // mRouter.setControlCategories(...);
- // routes = mRouter.getRoutes();
- // The current implementation returns empty list
- private volatile List<String> mControlCategories = Collections.emptyList();
+ @GuardedBy("sLock")
+ private List<String> mControlCategories = Collections.emptyList();
private MediaRoute2Info mSelectedRoute;
@GuardedBy("sLock")
@@ -117,7 +115,9 @@ public class MediaRouter2 {
private Client2 mClient;
final Handler mHandler;
- volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
+ @GuardedBy("sLock")
+ private boolean mShouldUpdateRoutes;
+ private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
/**
* Gets an instance of the media router associated with the context.
@@ -171,8 +171,7 @@ public class MediaRouter2 {
/**
* Registers a callback to discover routes and to receive events when they change.
* <p>
- * If you register the same callback twice or more, the previous arguments will be overwritten
- * with the new arguments.
+ * If you register the same callback twice or more, it will be ignored.
* </p>
*/
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@@ -180,18 +179,10 @@ public class MediaRouter2 {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
- CallbackRecord record;
- // This is required to prevent adding the same callback twice.
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- record = new CallbackRecord(callback);
- mCallbackRecords.add(record);
- } else {
- record = mCallbackRecords.get(index);
- }
- record.mExecutor = executor;
- record.mFlags = flags;
+ CallbackRecord record = new CallbackRecord(callback, executor, flags);
+ if (!mCallbackRecords.addIfAbsent(record)) {
+ Log.w(TAG, "Ignoring the same callback");
+ return;
}
synchronized (sLock) {
@@ -206,8 +197,6 @@ public class MediaRouter2 {
}
}
}
- //TODO: Is it thread-safe?
- record.notifyRoutes();
//TODO: Update discovery request here.
}
@@ -222,23 +211,20 @@ public class MediaRouter2 {
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- Log.w(TAG, "Ignoring to remove unknown callback. " + callback);
- return;
- }
- mCallbackRecords.remove(index);
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterClient2(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router.", ex);
- }
- //TODO: Clean up mRoutes. (onHandler?)
- mClient = null;
+ if (!mCallbackRecords.remove(new CallbackRecord(callback, null, 0))) {
+ Log.w(TAG, "Ignoring unknown callback");
+ return;
+ }
+
+ synchronized (sLock) {
+ if (mCallbackRecords.size() == 0 && mClient != null) {
+ try {
+ mMediaRouterService.unregisterClient2(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router.", ex);
}
+ //TODO: Clean up mRoutes. (onHandler?)
+ mClient = null;
}
}
}
@@ -246,26 +232,52 @@ public class MediaRouter2 {
//TODO(b/139033746): Rename "Control Category" when it's finalized.
/**
* Sets the control categories of the application.
- * Routes that support at least one of the given control categories only exists and are handled
+ * Routes that support at least one of the given control categories are handled
* by the media router.
*/
public void setControlCategories(@NonNull Collection<String> controlCategories) {
Objects.requireNonNull(controlCategories, "control categories must not be null");
- // To ensure invoking callbacks correctly according to control categories
- mHandler.sendMessage(obtainMessage(MediaRouter2::setControlCategoriesOnHandler,
- MediaRouter2.this, new ArrayList<>(controlCategories)));
+ List<String> newControlCategories = new ArrayList<>(controlCategories);
+
+ synchronized (sLock) {
+ mShouldUpdateRoutes = true;
+
+ // invoke callbacks due to control categories change
+ handleControlCategoriesChangedLocked(newControlCategories);
+ if (mClient != null) {
+ try {
+ mMediaRouterService.setControlCategories(mClient, mControlCategories);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to set control categories.", ex);
+ }
+ }
+ }
}
/**
* Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
* known to the media router.
+ * Please note that the list can be changed before callbacks are invoked.
*
* @return the list of routes that support at least one of the control categories set by
* the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
+ synchronized (sLock) {
+ if (mShouldUpdateRoutes) {
+ mShouldUpdateRoutes = false;
+
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : mRoutes.values()) {
+ if (route.supportsControlCategory(mControlCategories)) {
+ filteredRoutes.add(route);
+ }
+ }
+ mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
+ }
+ }
return mFilteredRoutes;
}
@@ -379,43 +391,16 @@ public class MediaRouter2 {
}
}
- @GuardedBy("mCallbackRecords")
- private int findCallbackRecordIndexLocked(Callback callback) {
- final int count = mCallbackRecords.size();
- for (int i = 0; i < count; i++) {
- CallbackRecord callbackRecord = mCallbackRecords.get(i);
- if (callbackRecord.mCallback == callback) {
- return i;
- }
- }
- return -1;
- }
-
- private void setControlCategoriesOnHandler(List<String> newControlCategories) {
- List<String> prevControlCategories = mControlCategories;
+ private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ List<String> prevControlCategories = mControlCategories;
mControlCategories = newControlCategories;
- Client2 client;
- synchronized (sLock) {
- client = mClient;
- }
- if (client != null) {
- try {
- mMediaRouterService.setControlCategories(client, mControlCategories);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to set control categories.", ex);
- }
- }
for (MediaRoute2Info route : mRoutes.values()) {
boolean preSupported = route.supportsControlCategory(prevControlCategories);
boolean postSupported = route.supportsControlCategory(newControlCategories);
- if (postSupported) {
- filteredRoutes.add(route);
- }
if (preSupported == postSupported) {
continue;
}
@@ -425,13 +410,14 @@ public class MediaRouter2 {
addedRoutes.add(route);
}
}
- mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
if (removedRoutes.size() > 0) {
- notifyRoutesRemoved(removedRoutes);
+ mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
+ MediaRouter2.this, removedRoutes));
}
if (addedRoutes.size() > 0) {
- notifyRoutesAdded(addedRoutes);
+ mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
+ MediaRouter2.this, addedRoutes));
}
}
@@ -441,42 +427,47 @@ public class MediaRouter2 {
// 2) Call onRouteSelected(system_route, reason_fallback) if previously selected route
// does not exist anymore. => We may need 'boolean MediaRoute2Info#isSystemRoute()'.
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategory(mControlCategories)) {
- addedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.put(route.getUniqueId(), route);
+ if (route.supportsControlCategory(mControlCategories)) {
+ addedRoutes.add(route);
+ }
}
+ mShouldUpdateRoutes = true;
}
if (addedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesAdded(addedRoutes);
}
}
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
- if (route.supportsControlCategory(mControlCategories)) {
- removedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.remove(route.getUniqueId());
+ if (route.supportsControlCategory(mControlCategories)) {
+ removedRoutes.add(route);
+ }
}
+ mShouldUpdateRoutes = true;
}
if (removedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesRemoved(removedRoutes);
}
}
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategory(mControlCategories)) {
- changedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.put(route.getUniqueId(), route);
+ if (route.supportsControlCategory(mControlCategories)) {
+ changedRoutes.add(route);
+ }
}
}
if (changedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesChanged(changedRoutes);
}
}
@@ -500,17 +491,6 @@ public class MediaRouter2 {
notifyRouteSelected(route, reason, controlHints);
}
- private void refreshFilteredRoutes() {
- List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
-
- for (MediaRoute2Info route : mRoutes.values()) {
- if (route.supportsControlCategory(mControlCategories)) {
- filteredRoutes.add(route);
- }
- }
- mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
- }
-
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
@@ -544,13 +524,16 @@ public class MediaRouter2 {
*/
public static class Callback {
/**
- * Called when routes are added.
+ * Called when routes are added. Whenever you registers a callback, this will
+ * be invoked with known routes.
+ *
* @param routes the list of routes that have been added. It's never empty.
*/
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are removed.
+ *
* @param routes the list of routes that have been removed. It's never empty.
*/
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
@@ -569,6 +552,7 @@ public class MediaRouter2 {
/**
* Called when a route is selected. Exactly one route can be selected at a time.
+ *
* @param route the selected route.
* @param reason the reason why the route is selected.
* @param controlHints An optional bundle of provider-specific arguments which may be
@@ -587,16 +571,26 @@ public class MediaRouter2 {
public Executor mExecutor;
public int mFlags;
- CallbackRecord(@NonNull Callback callback) {
+ CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
mCallback = callback;
+ mExecutor = executor;
+ mFlags = flags;
}
- void notifyRoutes() {
- final List<MediaRoute2Info> routes = mFilteredRoutes;
- // notify only when bound to media router service.
- if (routes.size() > 0) {
- mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
+ if (!(obj instanceof CallbackRecord)) {
+ return false;
+ }
+ return mCallback == ((CallbackRecord) obj).mCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallback.hashCode();
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index d56dd11fb8f2..502538d375ac 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -57,7 +57,7 @@ public class MediaRouter2Manager {
private Client mClient;
private final IMediaRouterService mMediaRouterService;
final Handler mHandler;
- final List<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
+ final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
private final Object mRoutesLock = new Object();
@GuardedBy("mRoutesLock")
@@ -99,14 +99,10 @@ public class MediaRouter2Manager {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
- CallbackRecord callbackRecord;
- synchronized (mCallbackRecords) {
- if (findCallbackRecordIndexLocked(callback) >= 0) {
- Log.w(TAG, "Ignoring to add the same callback twice.");
- return;
- }
- callbackRecord = new CallbackRecord(executor, callback);
- mCallbackRecords.add(callbackRecord);
+ CallbackRecord callbackRecord = new CallbackRecord(executor, callback);
+ if (!mCallbackRecords.addIfAbsent(callbackRecord)) {
+ Log.w(TAG, "Ignoring to add the same callback twice.");
+ return;
}
synchronized (sLock) {
@@ -118,8 +114,6 @@ public class MediaRouter2Manager {
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router manager.", ex);
}
- } else {
- callbackRecord.notifyRoutes();
}
}
}
@@ -132,36 +126,23 @@ public class MediaRouter2Manager {
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- Log.w(TAG, "Ignore removing unknown callback. " + callback);
- return;
- }
- mCallbackRecords.remove(index);
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterManager(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router manager", ex);
- }
- //TODO: clear mRoutes?
- mClient = null;
- }
- }
+ if (!mCallbackRecords.remove(new CallbackRecord(null, callback))) {
+ Log.w(TAG, "Ignore removing unknown callback. " + callback);
+ return;
}
- }
- @GuardedBy("mCallbackRecords")
- private int findCallbackRecordIndexLocked(Callback callback) {
- final int count = mCallbackRecords.size();
- for (int i = 0; i < count; i++) {
- if (mCallbackRecords.get(i).mCallback == callback) {
- return i;
+ synchronized (sLock) {
+ if (mCallbackRecords.size() == 0 && mClient != null) {
+ try {
+ mMediaRouterService.unregisterManager(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router manager", ex);
+ }
+ //TODO: clear mRoutes?
+ mClient = null;
+ mControlCategoryMap.clear();
}
}
- return -1;
}
//TODO: Use cache not to create array. For now, it's unclear when to purge the cache.
@@ -187,7 +168,6 @@ public class MediaRouter2Manager {
}
}
}
- //TODO: Should we cache this?
return routes;
}
@@ -342,10 +322,14 @@ public class MediaRouter2Manager {
}
void updateControlCategories(String packageName, List<String> categories) {
- mControlCategoryMap.put(packageName, categories);
+ List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
+ if ((prevCategories == null && categories.size() == 0)
+ || Objects.equals(categories, prevCategories)) {
+ return;
+ }
for (CallbackRecord record : mCallbackRecords) {
record.mExecutor.execute(
- () -> record.mCallback.onControlCategoriesChanged(packageName));
+ () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
}
}
@@ -386,8 +370,10 @@ public class MediaRouter2Manager {
* Called when the control categories of an app is changed.
*
* @param packageName the package name of the application
+ * @param controlCategories the list of control categories set by an application.
*/
- public void onControlCategoriesChanged(@NonNull String packageName) {}
+ public void onControlCategoriesChanged(@NonNull String packageName,
+ @NonNull List<String> controlCategories) {}
}
final class CallbackRecord {
@@ -399,14 +385,20 @@ public class MediaRouter2Manager {
mCallback = callback;
}
- void notifyRoutes() {
- List<MediaRoute2Info> routes;
- synchronized (mRoutesLock) {
- routes = new ArrayList<>(mRoutes.values());
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
- if (routes.size() > 0) {
- mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+ if (!(obj instanceof CallbackRecord)) {
+ return false;
}
+ return mCallback == ((CallbackRecord) obj).mCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallback.hashCode();
}
}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 515f6a8ce8a2..40e90731f2a2 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -19,6 +19,7 @@ package android.media;
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ServiceConnection;
import android.net.Uri;
@@ -197,7 +198,7 @@ public class MediaScannerConnection implements ServiceConnection {
private static Uri scanFileQuietly(ContentProviderClient client, File file) {
Uri uri = null;
try {
- uri = MediaStore.scanFile(client, file.getCanonicalFile());
+ uri = MediaStore.scanFile(ContentResolver.wrap(client), file.getCanonicalFile());
Log.d(TAG, "Scanned " + file + " to " + uri);
} catch (Exception e) {
Log.w(TAG, "Failed to scan " + file + ": " + e);
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 9064e6891be6..a1e159174f95 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -908,7 +908,7 @@ public class RingtoneManager {
}
// Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
- return MediaStore.scanFile(mContext, outFile);
+ return MediaStore.scanFile(mContext.getContentResolver(), outFile);
}
private static final String getExternalDirectoryForType(final int type) {
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
new file mode 100644
index 000000000000..5ff721837573
--- /dev/null
+++ b/media/java/android/media/RouteSessionController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 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 android.media;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * A class to control media route session in media route provider.
+ * For example, adding/removing/transferring routes to session can be done through this class.
+ * Instances are created by {@link MediaRouter2}.
+ *
+ * TODO: When session is introduced, change Javadoc of all methods/classes by using [@link Session].
+ *
+ * @hide
+ */
+public class RouteSessionController {
+ private final int mSessionId;
+ private final String mCategory;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+ new CopyOnWriteArrayList<>();
+
+ private volatile boolean mIsReleased;
+
+ /**
+ * @param sessionId the ID of the session.
+ * @param category The category of media routes that the session includes.
+ */
+ RouteSessionController(int sessionId, @NonNull String category) {
+ mSessionId = sessionId;
+ mCategory = category;
+ }
+
+ /**
+ * @return the ID of this controller
+ */
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * @return the category of routes that the session includes.
+ */
+ @NonNull
+ public String getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * @return the list of currently connected routes
+ */
+ @NonNull
+ public List<MediaRoute2Info> getRoutes() {
+ // TODO: Implement this when SessionInfo is introduced.
+ return null;
+ }
+
+ /**
+ * Returns true if the session is released, false otherwise.
+ * If it is released, then all other getters from this instance may return invalid values.
+ * Also, any operations to this instance will be ignored once released.
+ *
+ * @see #release
+ * @see Callback#onReleased
+ */
+ public boolean isReleased() {
+ return mIsReleased;
+ }
+
+ /**
+ * Add routes to the remote session.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void addRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Remove routes from this session. Media may be stopped on those devices.
+ * Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void removeRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Registers a {@link Callback} for monitoring route changes.
+ * If the same callback is registered previously, previous executor will be overwritten with the
+ * new one.
+ */
+ public void registerCallback(Executor executor, Callback callback) {
+ if (mIsReleased) {
+ return;
+ }
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordWithSameCallback = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordWithSameCallback = record;
+ break;
+ }
+ }
+
+ if (recordWithSameCallback != null) {
+ recordWithSameCallback.mExecutor = executor;
+ } else {
+ mCallbackRecords.add(new CallbackRecord(executor, callback));
+ }
+ }
+ }
+
+ /**
+ * Unregisters a previously registered {@link Callback}.
+ */
+ public void unregisterCallback(Callback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordToRemove = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordToRemove = record;
+ break;
+ }
+ }
+
+ if (recordToRemove != null) {
+ mCallbackRecords.remove(recordToRemove);
+ }
+ }
+ }
+
+ /**
+ * Release this session.
+ * Any operation on this session after calling this method will be ignored.
+ *
+ * @param stopMedia Should the device where the media is played
+ * be stopped after this session is released.
+ */
+ public void release(boolean stopMedia) {
+ mIsReleased = true;
+ mCallbackRecords.clear();
+ // TODO: Use stopMedia variable when the actual connection logic is implemented.
+ }
+
+ /**
+ * Callback class for getting updates on routes and session release.
+ */
+ public static class Callback {
+
+ /**
+ * Called when the session info has changed.
+ * TODO: When SessionInfo is introduced, uncomment below argument.
+ */
+ void onSessionInfoChanged(/* SessionInfo info */) {}
+
+ /**
+ * Called when the session is released. Session can be released by the controller using
+ * {@link #release(boolean)}, or by the {@link MediaRoute2ProviderService} itself.
+ * One can do clean-ups here.
+ *
+ * TODO: When SessionInfo is introduced, change the javadoc of releasing session on
+ * provider side.
+ */
+ void onReleased(int reason, boolean shouldStop) {}
+ }
+
+ private class CallbackRecord {
+ public final Callback mCallback;
+ public Executor mExecutor;
+
+ CallbackRecord(@NonNull Executor executor, @NonNull Callback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+ }
+}
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index a315c1eefb52..6705b0cb79f0 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -33,14 +33,13 @@ import android.graphics.ImageDecoder;
import android.graphics.ImageDecoder.ImageInfo;
import android.graphics.ImageDecoder.Source;
import android.graphics.Matrix;
-import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore.ThumbnailConstants;
+import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
@@ -77,15 +76,7 @@ public class ThumbnailUtils {
public static final int OPTIONS_RECYCLE_INPUT = 0x2;
private static Size convertKind(int kind) {
- if (kind == ThumbnailConstants.MICRO_KIND) {
- return Point.convert(ThumbnailConstants.MICRO_SIZE);
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE);
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- return Point.convert(ThumbnailConstants.MINI_SIZE);
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ return MediaStore.Images.Thumbnails.getKindSize(kind);
}
private static class Resizer implements ImageDecoder.OnHeaderDecodedListener {
diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/java/android/media/audio/common/AudioChannelMask.aidl
new file mode 100644
index 000000000000..b9b08e6921bc
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioChannelMask.aidl
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * A channel mask per se only defines the presence or absence of a channel, not
+ * the order.
+ *
+ * The channel order convention is that channels are interleaved in order from
+ * least significant channel mask bit to most significant channel mask bit,
+ * with unused bits skipped. For example for stereo, LEFT would be first,
+ * followed by RIGHT.
+ * Any exceptions to this convention are noted at the appropriate API.
+ *
+ * AudioChannelMask is an opaque type and its internal layout should not be
+ * assumed as it may change in the future. Instead, always use functions
+ * to examine it.
+ *
+ * These are the current representations:
+ *
+ * REPRESENTATION_POSITION
+ * is a channel mask representation for position assignment. Each low-order
+ * bit corresponds to the spatial position of a transducer (output), or
+ * interpretation of channel (input). The user of a channel mask needs to
+ * know the context of whether it is for output or input. The constants
+ * OUT_* or IN_* apply to the bits portion. It is not permitted for no bits
+ * to be set.
+ *
+ * REPRESENTATION_INDEX
+ * is a channel mask representation for index assignment. Each low-order
+ * bit corresponds to a selected channel. There is no platform
+ * interpretation of the various bits. There is no concept of output or
+ * input. It is not permitted for no bits to be set.
+ *
+ * All other representations are reserved for future use.
+ *
+ * Warning: current representation distinguishes between input and output, but
+ * this will not the be case in future revisions of the platform. Wherever there
+ * is an ambiguity between input and output that is currently resolved by
+ * checking the channel mask, the implementer should look for ways to fix it
+ * with additional information outside of the mask.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioChannelMask {
+ /**
+ * must be 0 for compatibility
+ */
+ REPRESENTATION_POSITION = 0,
+ /**
+ * 1 is reserved for future use
+ */
+ REPRESENTATION_INDEX = 2,
+ /**
+ * 3 is reserved for future use
+ *
+ *
+ * These can be a complete value of AudioChannelMask
+ */
+ NONE = 0x0,
+ INVALID = 0xC0000000,
+ /**
+ * These can be the bits portion of an AudioChannelMask
+ * with representation REPRESENTATION_POSITION.
+ *
+ *
+ * output channels
+ */
+ OUT_FRONT_LEFT = 0x1,
+ OUT_FRONT_RIGHT = 0x2,
+ OUT_FRONT_CENTER = 0x4,
+ OUT_LOW_FREQUENCY = 0x8,
+ OUT_BACK_LEFT = 0x10,
+ OUT_BACK_RIGHT = 0x20,
+ OUT_FRONT_LEFT_OF_CENTER = 0x40,
+ OUT_FRONT_RIGHT_OF_CENTER = 0x80,
+ OUT_BACK_CENTER = 0x100,
+ OUT_SIDE_LEFT = 0x200,
+ OUT_SIDE_RIGHT = 0x400,
+ OUT_TOP_CENTER = 0x800,
+ OUT_TOP_FRONT_LEFT = 0x1000,
+ OUT_TOP_FRONT_CENTER = 0x2000,
+ OUT_TOP_FRONT_RIGHT = 0x4000,
+ OUT_TOP_BACK_LEFT = 0x8000,
+ OUT_TOP_BACK_CENTER = 0x10000,
+ OUT_TOP_BACK_RIGHT = 0x20000,
+ OUT_TOP_SIDE_LEFT = 0x40000,
+ OUT_TOP_SIDE_RIGHT = 0x80000,
+ /**
+ * Haptic channel characteristics are specific to a device and
+ * only used to play device specific resources (eg: ringtones).
+ * The HAL can freely map A and B to haptic controllers, the
+ * framework shall not interpret those values and forward them
+ * from the device audio assets.
+ */
+ OUT_HAPTIC_A = 0x20000000,
+ OUT_HAPTIC_B = 0x10000000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// OUT_MONO = OUT_FRONT_LEFT,
+// OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
+// OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_QUAD_BACK = OUT_QUAD,
+// /**
+// * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
+// OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
+// OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_5POINT1_BACK = OUT_5POINT1,
+// /**
+// * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
+// /**
+// * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
+// */
+// OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
+// OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
+// OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+ /**
+ * These are bits only, not complete values
+ *
+ *
+ * input channels
+ */
+ IN_LEFT = 0x4,
+ IN_RIGHT = 0x8,
+ IN_FRONT = 0x10,
+ IN_BACK = 0x20,
+ IN_LEFT_PROCESSED = 0x40,
+ IN_RIGHT_PROCESSED = 0x80,
+ IN_FRONT_PROCESSED = 0x100,
+ IN_BACK_PROCESSED = 0x200,
+ IN_PRESSURE = 0x400,
+ IN_X_AXIS = 0x800,
+ IN_Y_AXIS = 0x1000,
+ IN_Z_AXIS = 0x2000,
+ IN_BACK_LEFT = 0x10000,
+ IN_BACK_RIGHT = 0x20000,
+ IN_CENTER = 0x40000,
+ IN_LOW_FREQUENCY = 0x100000,
+ IN_TOP_LEFT = 0x200000,
+ IN_TOP_RIGHT = 0x400000,
+ IN_VOICE_UPLINK = 0x4000,
+ IN_VOICE_DNLINK = 0x8000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// IN_MONO = IN_FRONT,
+// IN_STEREO = (IN_LEFT | IN_RIGHT),
+// IN_FRONT_BACK = (IN_FRONT | IN_BACK),
+// IN_6 = (IN_LEFT | IN_RIGHT | IN_FRONT | IN_BACK | IN_LEFT_PROCESSED | IN_RIGHT_PROCESSED),
+// IN_2POINT0POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_2POINT1POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_3POINT0POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_3POINT1POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_5POINT1 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_BACK_LEFT | IN_BACK_RIGHT | IN_LOW_FREQUENCY),
+// IN_VOICE_UPLINK_MONO = (IN_VOICE_UPLINK | IN_MONO),
+// IN_VOICE_DNLINK_MONO = (IN_VOICE_DNLINK | IN_MONO),
+// IN_VOICE_CALL_MONO = (IN_VOICE_UPLINK_MONO | IN_VOICE_DNLINK_MONO),
+// COUNT_MAX = 30,
+// INDEX_HDR = REPRESENTATION_INDEX << COUNT_MAX,
+// INDEX_MASK_1 = INDEX_HDR | ((1 << 1) - 1),
+// INDEX_MASK_2 = INDEX_HDR | ((1 << 2) - 1),
+// INDEX_MASK_3 = INDEX_HDR | ((1 << 3) - 1),
+// INDEX_MASK_4 = INDEX_HDR | ((1 << 4) - 1),
+// INDEX_MASK_5 = INDEX_HDR | ((1 << 5) - 1),
+// INDEX_MASK_6 = INDEX_HDR | ((1 << 6) - 1),
+// INDEX_MASK_7 = INDEX_HDR | ((1 << 7) - 1),
+// INDEX_MASK_8 = INDEX_HDR | ((1 << 8) - 1),
+// INDEX_MASK_9 = INDEX_HDR | ((1 << 9) - 1),
+// INDEX_MASK_10 = INDEX_HDR | ((1 << 10) - 1),
+// INDEX_MASK_11 = INDEX_HDR | ((1 << 11) - 1),
+// INDEX_MASK_12 = INDEX_HDR | ((1 << 12) - 1),
+// INDEX_MASK_13 = INDEX_HDR | ((1 << 13) - 1),
+// INDEX_MASK_14 = INDEX_HDR | ((1 << 14) - 1),
+// INDEX_MASK_15 = INDEX_HDR | ((1 << 15) - 1),
+// INDEX_MASK_16 = INDEX_HDR | ((1 << 16) - 1),
+// INDEX_MASK_17 = INDEX_HDR | ((1 << 17) - 1),
+// INDEX_MASK_18 = INDEX_HDR | ((1 << 18) - 1),
+// INDEX_MASK_19 = INDEX_HDR | ((1 << 19) - 1),
+// INDEX_MASK_20 = INDEX_HDR | ((1 << 20) - 1),
+// INDEX_MASK_21 = INDEX_HDR | ((1 << 21) - 1),
+// INDEX_MASK_22 = INDEX_HDR | ((1 << 22) - 1),
+// INDEX_MASK_23 = INDEX_HDR | ((1 << 23) - 1),
+// INDEX_MASK_24 = INDEX_HDR | ((1 << 24) - 1),
+}
diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/java/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 000000000000..50dd796e1fa0
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioConfig.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioOffloadInfo;
+
+/**
+ * Commonly used audio stream configuration parameters.
+ *
+ * {@hide}
+ */
+parcelable AudioConfig {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioOffloadInfo offloadInfo;
+ long frameCount;
+}
diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/java/android/media/audio/common/AudioFormat.aidl
new file mode 100644
index 000000000000..aadc8e26cce3
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioFormat.aidl
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio format is a 32-bit word that consists of:
+ * main format field (upper 8 bits)
+ * sub format field (lower 24 bits).
+ *
+ * The main format indicates the main codec type. The sub format field indicates
+ * options and parameters for each format. The sub format is mainly used for
+ * record to indicate for instance the requested bitrate or profile. It can
+ * also be used for certain formats to give informations not present in the
+ * encoded audio stream (e.g. octet alignement for AMR).
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioFormat {
+ INVALID = 0xFFFFFFFF,
+ DEFAULT = 0,
+ PCM = 0x00000000,
+ MP3 = 0x01000000,
+ AMR_NB = 0x02000000,
+ AMR_WB = 0x03000000,
+ AAC = 0x04000000,
+ /**
+ * Deprecated, Use AAC_HE_V1
+ */
+ HE_AAC_V1 = 0x05000000,
+ /**
+ * Deprecated, Use AAC_HE_V2
+ */
+ HE_AAC_V2 = 0x06000000,
+ VORBIS = 0x07000000,
+ OPUS = 0x08000000,
+ AC3 = 0x09000000,
+ E_AC3 = 0x0A000000,
+ DTS = 0x0B000000,
+ DTS_HD = 0x0C000000,
+ /**
+ * IEC61937 is encoded audio wrapped in 16-bit PCM.
+ */
+ IEC61937 = 0x0D000000,
+ DOLBY_TRUEHD = 0x0E000000,
+ EVRC = 0x10000000,
+ EVRCB = 0x11000000,
+ EVRCWB = 0x12000000,
+ EVRCNW = 0x13000000,
+ AAC_ADIF = 0x14000000,
+ WMA = 0x15000000,
+ WMA_PRO = 0x16000000,
+ AMR_WB_PLUS = 0x17000000,
+ MP2 = 0x18000000,
+ QCELP = 0x19000000,
+ DSD = 0x1A000000,
+ FLAC = 0x1B000000,
+ ALAC = 0x1C000000,
+ APE = 0x1D000000,
+ AAC_ADTS = 0x1E000000,
+ SBC = 0x1F000000,
+ APTX = 0x20000000,
+ APTX_HD = 0x21000000,
+ AC4 = 0x22000000,
+ LDAC = 0x23000000,
+ /**
+ * Dolby Metadata-enhanced Audio Transmission
+ */
+ MAT = 0x24000000,
+ AAC_LATM = 0x25000000,
+ CELT = 0x26000000,
+ APTX_ADAPTIVE = 0x27000000,
+ LHDC = 0x28000000,
+ LHDC_LL = 0x29000000,
+ APTX_TWSP = 0x2A000000,
+ /**
+ * Deprecated
+ */
+ MAIN_MASK = 0xFF000000,
+ SUB_MASK = 0x00FFFFFF,
+ /**
+ * Subformats
+ */
+ PCM_SUB_16_BIT = 0x1,
+ PCM_SUB_8_BIT = 0x2,
+ PCM_SUB_32_BIT = 0x3,
+ PCM_SUB_8_24_BIT = 0x4,
+ PCM_SUB_FLOAT = 0x5,
+ PCM_SUB_24_BIT_PACKED = 0x6,
+ MP3_SUB_NONE = 0x0,
+ AMR_SUB_NONE = 0x0,
+ AAC_SUB_MAIN = 0x1,
+ AAC_SUB_LC = 0x2,
+ AAC_SUB_SSR = 0x4,
+ AAC_SUB_LTP = 0x8,
+ AAC_SUB_HE_V1 = 0x10,
+ AAC_SUB_SCALABLE = 0x20,
+ AAC_SUB_ERLC = 0x40,
+ AAC_SUB_LD = 0x80,
+ AAC_SUB_HE_V2 = 0x100,
+ AAC_SUB_ELD = 0x200,
+ AAC_SUB_XHE = 0x300,
+ VORBIS_SUB_NONE = 0x0,
+ E_AC3_SUB_JOC = 0x1,
+ MAT_SUB_1_0 = 0x1,
+ MAT_SUB_2_0 = 0x2,
+ MAT_SUB_2_1 = 0x3,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// /**
+// * Aliases
+// *
+// *
+// * note != AudioFormat.ENCODING_PCM_16BIT
+// */
+// PCM_16_BIT = (PCM | PCM_SUB_16_BIT),
+// /**
+// * note != AudioFormat.ENCODING_PCM_8BIT
+// */
+// PCM_8_BIT = (PCM | PCM_SUB_8_BIT),
+// PCM_32_BIT = (PCM | PCM_SUB_32_BIT),
+// PCM_8_24_BIT = (PCM | PCM_SUB_8_24_BIT),
+// PCM_FLOAT = (PCM | PCM_SUB_FLOAT),
+// PCM_24_BIT_PACKED = (PCM | PCM_SUB_24_BIT_PACKED),
+// AAC_MAIN = (AAC | AAC_SUB_MAIN),
+// AAC_LC = (AAC | AAC_SUB_LC),
+// AAC_SSR = (AAC | AAC_SUB_SSR),
+// AAC_LTP = (AAC | AAC_SUB_LTP),
+// AAC_HE_V1 = (AAC | AAC_SUB_HE_V1),
+// AAC_SCALABLE = (AAC | AAC_SUB_SCALABLE),
+// AAC_ERLC = (AAC | AAC_SUB_ERLC),
+// AAC_LD = (AAC | AAC_SUB_LD),
+// AAC_HE_V2 = (AAC | AAC_SUB_HE_V2),
+// AAC_ELD = (AAC | AAC_SUB_ELD),
+// AAC_XHE = (AAC | AAC_SUB_XHE),
+// AAC_ADTS_MAIN = (AAC_ADTS | AAC_SUB_MAIN),
+// AAC_ADTS_LC = (AAC_ADTS | AAC_SUB_LC),
+// AAC_ADTS_SSR = (AAC_ADTS | AAC_SUB_SSR),
+// AAC_ADTS_LTP = (AAC_ADTS | AAC_SUB_LTP),
+// AAC_ADTS_HE_V1 = (AAC_ADTS | AAC_SUB_HE_V1),
+// AAC_ADTS_SCALABLE = (AAC_ADTS | AAC_SUB_SCALABLE),
+// AAC_ADTS_ERLC = (AAC_ADTS | AAC_SUB_ERLC),
+// AAC_ADTS_LD = (AAC_ADTS | AAC_SUB_LD),
+// AAC_ADTS_HE_V2 = (AAC_ADTS | AAC_SUB_HE_V2),
+// AAC_ADTS_ELD = (AAC_ADTS | AAC_SUB_ELD),
+// AAC_ADTS_XHE = (AAC_ADTS | AAC_SUB_XHE),
+// E_AC3_JOC = (E_AC3 | E_AC3_SUB_JOC),
+// MAT_1_0 = (MAT | MAT_SUB_1_0),
+// MAT_2_0 = (MAT | MAT_SUB_2_0),
+// MAT_2_1 = (MAT | MAT_SUB_2_1),
+// AAC_LATM_LC = (AAC_LATM | AAC_SUB_LC),
+// AAC_LATM_HE_V1 = (AAC_LATM | AAC_SUB_HE_V1),
+// AAC_LATM_HE_V2 = (AAC_LATM | AAC_SUB_HE_V2),
+}
diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 000000000000..ec10d71135ae
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioStreamType;
+import android.media.audio.common.AudioUsage;
+
+/**
+ * Additional information about the stream passed to hardware decoders.
+ *
+ * {@hide}
+ */
+parcelable AudioOffloadInfo {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioStreamType streamType;
+ int bitRatePerSecond;
+ long durationMicroseconds;
+ boolean hasVideo;
+ boolean isStreaming;
+ int bitWidth;
+ int bufferSize;
+ AudioUsage usage;
+}
+
diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/java/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 000000000000..c54566726350
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio streams
+ *
+ * Audio stream type describing the intended use case of a stream.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioStreamType {
+ DEFAULT = -1,
+ MIN = 0,
+ VOICE_CALL = 0,
+ SYSTEM = 1,
+ RING = 2,
+ MUSIC = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ BLUETOOTH_SCO = 6,
+ ENFORCED_AUDIBLE = 7,
+ DTMF = 8,
+ TTS = 9,
+ ACCESSIBILITY = 10,
+}
diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/java/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 000000000000..ef348165b22c
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioUsage.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioUsage {
+ UNKNOWN = 0,
+ MEDIA = 1,
+ VOICE_COMMUNICATION = 2,
+ VOICE_COMMUNICATION_SIGNALLING = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ NOTIFICATION_TELEPHONY_RINGTONE = 6,
+ ASSISTANCE_ACCESSIBILITY = 11,
+ ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+ ASSISTANCE_SONIFICATION = 13,
+ GAME = 14,
+ VIRTUAL_SOURCE = 15,
+ ASSISTANT = 16,
+}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 56e5566df29c..77596a5de815 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -22,7 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 61b3e76e7cee..1c38301c7935 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -23,7 +23,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.soundtrigger.ModelParams;
diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
new file mode 100644
index 000000000000..3dbc70556bd3
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * A recognition confidence level.
+ * This type is used to represent either a threshold or an actual detection confidence level.
+ *
+ * {@hide}
+ */
+parcelable ConfidenceLevel {
+ /** user ID. */
+ int userId;
+ /**
+ * Confidence level in percent (0 - 100).
+ * <ul>
+ * <li>Min level for recognition configuration
+ * <li>Detected level for recognition event.
+ * </ul>
+ */
+ int levelPercent;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
new file mode 100644
index 000000000000..149c1cd0447b
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+
+/**
+ * Main interface for a client to get notifications of events coming from this module.
+ *
+ * {@hide}
+ */
+oneway interface ISoundTriggerCallback {
+ /**
+ * Invoked whenever a recognition event is triggered (typically, on recognition, but also in
+ * case of external aborting of a recognition or a forced recognition event - see the status
+ * code in the event for determining).
+ */
+ void onRecognition(int modelHandle, in RecognitionEvent event);
+ /**
+ * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
+ * also in case of external aborting of a recognition or a forced recognition event - see the
+ * status code in the event for determining).
+ */
+ void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event);
+ /**
+ * Notifies the client the recognition has become available after previously having been
+ * unavailable, or vice versa. This method will always be invoked once immediately after
+ * attachment, and then every time there is a change in availability.
+ * When availability changes from available to unavailable, all active recognitions are aborted,
+ * and this event will be sent in addition to the abort event.
+ */
+ void onRecognitionAvailabilityChange(boolean available);
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
new file mode 100644
index 000000000000..80333070b7ce
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+
+/**
+ * Main entry point into this module.
+ *
+ * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
+ * attach to either one of them in order to use it.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerMiddlewareService {
+ /**
+ * Query the available modules and their capabilities.
+ */
+ SoundTriggerModuleDescriptor[] listModules();
+
+ /**
+ * Attach to one of the available modules.
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
+
+ /**
+ * Notify the service that external input capture is taking place. This may cause some of the
+ * active recognitions to be aborted.
+ */
+ void setExternalCaptureState(boolean active);
+} \ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
new file mode 100644
index 000000000000..c4a57857dd3d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -0,0 +1,148 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+
+/**
+ * A sound-trigger module.
+ *
+ * This interface allows a client to operate a sound-trigger device, intended for low-power
+ * detection of various sound patterns, represented by a "sound model".
+ *
+ * Basic operation is to load a sound model (either a generic one or a "phrase" model), then
+ * initiate recognition on this model. A trigger will be delivered asynchronously via a callback
+ * provided by the caller earlier, when attaching to this interface.
+ *
+ * In additon to recognition events, this module will also produce abort events in cases where
+ * recognition has been externally preempted.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerModule {
+ /**
+ * Load a sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using {@link #unloadModel(int)} prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadModel(in SoundModel model);
+
+ /**
+ * Load a phrase sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using unloadModel prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadPhraseModel(in PhraseSoundModel model);
+
+ /**
+ * Unload a model, previously loaded with loadModel or loadPhraseModel. After unloading, model
+ * can no longer be used for recognition and the resources occupied by it are released.
+ * Model must not be active at the time of unloading. Cient may call stopRecognition to ensure
+ * that.
+ */
+ void unloadModel(int modelHandle);
+
+ /**
+ * Initiate recognition on a previously loaded model.
+ * Recognition event would eventually be delivered via the client-provided callback, typically
+ * supplied during attachment to this interface.
+ *
+ * Once a recognition event is passed to the client, the recognition automatically become
+ * inactive, unless the event is of the RecognitionStatus.FORCED kind. Client can also shut down
+ * the recognition explicitly, via stopRecognition.
+ */
+ void startRecognition(int modelHandle, in RecognitionConfig config);
+
+ /**
+ * Stop a recognition of a previously active recognition. Will NOT generate a recognition event.
+ * This call is idempotent - calling it on an inactive model has no effect. However, it must
+ * only be used with a loaded model handle.
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * Force generation of a recognition event. Handle must be that of a loaded model. If
+ * recognition is inactive, will do nothing. If recognition is active, will asynchronously
+ * deliever an event with RecognitionStatus.FORCED status and leave recognition in active state.
+ * To avoid any race conditions, once an event signalling the automatic stopping of recognition
+ * is sent, no more forced events will get sent (even if previously requested) until recognition
+ * is explicitly started again.
+ *
+ * Since not all module implementations support this feature, may throw a
+ * ServiceSpecificException with an OPERATION_NOT_SUPPORTED status.
+ */
+ void forceRecognitionEvent(int modelHandle);
+
+ /**
+ * Set a model specific parameter with the given value. This parameter
+ * will keep its value for the duration the model is loaded regardless of starting and stopping
+ * recognition. Once the model is unloaded, the value will be lost.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model handle indicating which model to modify parameters
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @param value The value to set for the given model parameter
+ */
+ void setModelParameter(int modelHandle, ModelParameter modelParam, int value);
+
+ /**
+ * Get a model specific parameter. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParameter for parameter default values.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model associated with given modelParam
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return Value set to the requested parameter.
+ */
+ int getModelParameter(int modelHandle, ModelParameter modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle, and its valid value
+ * range if it is.
+ *
+ * @param modelHandle The sound model handle indicating which model to query
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return If parameter is supported, the return value is its valid range, otherwise null.
+ */
+ @nullable ModelParameterRange queryModelParameterSupport(int modelHandle,
+ ModelParameter modelParam);
+
+ /**
+ * Detach from the module, releasing any active resources.
+ * This will ensure the client callback is no longer called after this call returns.
+ * All models must have been unloaded prior to calling this method.
+ */
+ void detach();
+} \ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
new file mode 100644
index 000000000000..09936278e93a
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParameter {
+ /**
+ * Placeholder for invalid model parameter used for returning error or
+ * passing an invalid value.
+ */
+ INVALID = -1,
+
+ /**
+ * Controls the sensitivity threshold adjustment factor for a given model.
+ * Negative value corresponds to less sensitive model (high threshold) and
+ * a positive value corresponds to a more sensitive model (low threshold).
+ * Default value is 0.
+ */
+ THRESHOLD_FACTOR = 0,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
new file mode 100644
index 000000000000..d6948a87dc6d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * Value range for a model parameter.
+ *
+ * {@hide}
+ */
+parcelable ModelParameterRange {
+ /** Minimum (inclusive) */
+ int minInclusive;
+ /** Maximum (inclusive) */
+ int maxInclusive;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
new file mode 100644
index 000000000000..98a489f8a6a9
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * Key phrase descriptor.
+ *
+ * {@hide}
+ */
+parcelable Phrase {
+ /** Unique keyphrase ID assigned at enrollment time. */
+ int id;
+ /** Recognition modes supported by this key phrase (bitfield of RecognitionMode enum). */
+ int recognitionModes;
+ /** List of users IDs associated with this key phrase. */
+ int[] users;
+ /** Locale - Java Locale style (e.g. en_US). */
+ String locale;
+ /** Phrase text. */
+ String text;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
new file mode 100644
index 000000000000..6a3ec61d1ebf
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+
+/**
+ * An event that gets sent to indicate a phrase recognition (or aborting of the recognition
+ process).
+ * {@hide}
+ */
+parcelable PhraseRecognitionEvent {
+ /** Common recognition event. */
+ RecognitionEvent common;
+ /** List of descriptors for each recognized key phrase */
+ PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
new file mode 100644
index 000000000000..cb96bf37a95d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+
+/**
+ * Specialized recognition event for key phrase detection.
+ * {@hide}
+ */
+parcelable PhraseRecognitionExtra {
+ // TODO(ytai): Constants / enums.
+
+ /** keyphrase ID */
+ int id;
+ /** recognition modes used for this keyphrase */
+ int recognitionModes;
+ /** confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER */
+ int confidenceLevel;
+ /** number of user confidence levels */
+ ConfidenceLevel[] levels;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
new file mode 100644
index 000000000000..81028c1608ea
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.Phrase;
+
+/**
+ * Specialized sound model for key phrase detection.
+ * Proprietary representation of key phrases in binary data must match
+ * information indicated by phrases field.
+ * {@hide}
+ */
+parcelable PhraseSoundModel {
+ /** Common part of sound model descriptor */
+ SoundModel common;
+ /** List of descriptors for key phrases supported by this sound model */
+ Phrase[] phrases;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
new file mode 100644
index 000000000000..c7642e8c1241
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+
+/**
+ * Configuration for tuning behavior of an active recognition process.
+ * {@hide}
+ */
+parcelable RecognitionConfig {
+ /* Capture and buffer audio for this recognition instance. */
+ boolean captureRequested;
+
+ /* Configuration for each key phrase. */
+ PhraseRecognitionExtra[] phraseRecognitionExtras;
+
+ /** Opaque capture configuration data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
new file mode 100644
index 000000000000..de4d060ce484
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * An event that gets sent to indicate a recognition (or aborting of the recognition process).
+ * {@hide}
+ */
+parcelable RecognitionEvent {
+ /** Recognition status. */
+ RecognitionStatus status;
+ /** Event type, same as sound model type. */
+ SoundModelType type;
+ /** Is it possible to capture audio from this utterance buffered by the implementation. */
+ boolean captureAvailable;
+ /* Audio session ID. framework use. */
+ int captureSession;
+ /**
+ * Delay in ms between end of model detection and start of audio available for capture.
+ * A negative value is possible (e.g. if key phrase is also available for Capture.
+ */
+ int captureDelayMs;
+ /** Duration in ms of audio captured before the start of the trigger. 0 if none. */
+ int capturePreambleMs;
+ /** If true, the 'data' field below contains the capture of the trigger sound. */
+ boolean triggerInData;
+ /**
+ * Audio format of either the trigger in event data or to use for capture of the rest of the
+ * utterance.
+ */
+ AudioConfig audioConfig;
+ /** Additional data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
new file mode 100644
index 000000000000..d8bfff4bec6f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * Recognition mode.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionMode {
+ /** Simple voice trigger. */
+ VOICE_TRIGGER = 0x1,
+ /** Trigger only if one user in model identified. */
+ USER_IDENTIFICATION = 0x2,
+ /** Trigger only if one user in model authenticated. */
+ USER_AUTHENTICATION = 0x4,
+ /** Generic sound trigger. */
+ GENERIC_TRIGGER = 0x8,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
new file mode 100644
index 000000000000..d563edca547d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * A status for indicating the type of a recognition event.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionStatus {
+ /** Recognition success. */
+ SUCCESS = 0,
+ /** Recognition aborted (e.g. capture preempted by another use-case. */
+ ABORTED = 1,
+ /** Recognition failure. */
+ FAILURE = 2,
+ /**
+ * Recognition event was triggered by a forceRecognitionEvent request, not by the DSP.
+ * Note that forced detections *do not* stop the active recognition, unlike the other types.
+ */
+ FORCED = 3
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
new file mode 100644
index 000000000000..fba1ee507836
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * Base sound model descriptor. This struct can be extended for various specific types by way of
+ * aggregation.
+ * {@hide}
+ */
+parcelable SoundModel {
+ /** Model type. */
+ SoundModelType type;
+ /** Unique sound model ID. */
+ String uuid;
+ /**
+ * Unique vendor ID. Identifies the engine the sound model
+ * was build for */
+ String vendorUuid;
+ /** Opaque data transparent to Android framework */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
new file mode 100644
index 000000000000..f2abc9af7780
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * Sound model type.
+ * {@hide}
+ */
+@Backing(type="int")
+enum SoundModelType {
+ /** Unspecified sound model type */
+ UNKNOWN = -1,
+ /** Key phrase sound models */
+ KEYPHRASE = 0,
+ /** All models other than keyphrase */
+ GENERIC = 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
new file mode 100644
index 000000000000..667135ff61b9
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+/**
+ * A descriptor of an available sound trigger module, containing the handle used to reference the
+ * module, as well its capabilities.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleDescriptor {
+ /** Module handle to be used for attaching to it. */
+ int handle;
+ /** Module capabilities. */
+ SoundTriggerModuleProperties properties;
+}
+
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
new file mode 100644
index 000000000000..1a3b40261a62
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -0,0 +1,53 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * Capabilities of a sound trigger module.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleProperties {
+ /** Implementor name */
+ String implementor;
+ /** Implementation description */
+ String description;
+ /** Implementation version */
+ int version;
+ /**
+ * Unique implementation ID. The UUID must change with each version of
+ the engine implementation */
+ String uuid;
+ /** Maximum number of concurrent sound models loaded */
+ int maxSoundModels;
+ /** Maximum number of key phrases */
+ int maxKeyPhrases;
+ /** Maximum number of concurrent users detected */
+ int maxUsers;
+ /** All supported modes. e.g RecognitionMode.VOICE_TRIGGER */
+ int recognitionModes;
+ /** Supports seamless transition from detection to capture */
+ boolean captureTransition;
+ /** Maximum buffering capacity in ms if captureTransition is true */
+ int maxBufferMs;
+ /** Supports capture by other use cases while detection is active */
+ boolean concurrentCapture;
+ /** Returns the trigger capture in event */
+ boolean triggerInEvent;
+ /**
+ * Rated power consumption when detection is active with TDB
+ * silence/sound/speech ratio */
+ int powerConsumptionMw;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
new file mode 100644
index 000000000000..d8f9d8f7e891
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum Status {
+ /** Success. */
+ SUCCESS = 0,
+ /** Failure due to resource contention. This is typically a temporary condition. */
+ RESOURCE_CONTENTION = 1,
+ /** Operation is not supported in this implementation. This is a permanent condition. */
+ OPERATION_NOT_SUPPORTED = 2,
+}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 1b9cac0c8c99..377b2bc19c6b 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -20,7 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringRes;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 5c11ed9bb7b4..7fbb3376d5fb 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -22,9 +22,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 0f402eb0bf42..f3c071a06eba 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -16,8 +16,10 @@
package android.mtp;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -422,13 +424,13 @@ public class MtpDatabase implements AutoCloseable {
}
// Add the new file to MediaProvider
if (succeeded) {
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
}
@VisibleForNative
private void rescanFile(String path, int handle, int format) {
- MediaStore.scanFile(mContext, new File(path));
+ MediaStore.scanFile(mContext.getContentResolver(), new File(path));
}
@VisibleForNative
@@ -587,13 +589,13 @@ public class MtpDatabase implements AutoCloseable {
if (obj.isDir()) {
// for directories, check if renamed from something hidden to something non-hidden
if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
- MediaStore.scanFile(mContext, newPath.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
}
} else {
// for files, check if renamed from .nomedia to something else
if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
&& !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
- MediaStore.scanFile(mContext, newPath.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
}
}
return MtpConstants.RESPONSE_OK;
@@ -662,7 +664,7 @@ public class MtpDatabase implements AutoCloseable {
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} else {
// Old parent doesn't exist - add the object
- MediaStore.scanFile(mContext, path.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -689,7 +691,7 @@ public class MtpDatabase implements AutoCloseable {
if (!success) {
return;
}
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
@VisibleForNative
@@ -909,7 +911,7 @@ public class MtpDatabase implements AutoCloseable {
String[] whereArgs = new String[]{path.toString()};
if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
- MediaStore.scanFile(mContext, path.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
}
} else {
Log.i(TAG, "Mediaprovider didn't delete " + path);
diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java
index 557f099c25c1..53d838d84518 100644
--- a/media/java/android/mtp/MtpPropertyList.java
+++ b/media/java/android/mtp/MtpPropertyList.java
@@ -16,7 +16,8 @@
package android.mtp;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 65d0fef74b25..9fd0a28c74b4 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -16,7 +16,7 @@
package android.mtp;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.storage.StorageVolume;
import android.provider.MediaStore;
@@ -41,11 +41,7 @@ public class MtpStorage {
mDescription = volume.getDescription(null);
mRemovable = volume.isRemovable();
mMaxFileSize = volume.getMaxFileSize();
- if (volume.isPrimary()) {
- mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
- } else {
- mVolumeName = volume.getNormalizedUuid();
- }
+ mVolumeName = volume.getMediaStoreVolumeName();
}
/**
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 86a1076af122..06adf30a8303 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -21,8 +21,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 05aaa82f8ac8..20980a90e9fe 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -742,11 +742,11 @@ status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const
return OK;
}
-status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
- mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply);
+status_t JMediaCodec::getMetrics(JNIEnv *, mediametrics::Item * &reply) const {
+ mediametrics_handle_t reply2 = mediametrics::Item::convert(reply);
status_t status = mCodec->getMetrics(reply2);
// getMetrics() updates reply2, pass the converted update along to our caller.
- reply = MediaAnalyticsItem::convert(reply2);
+ reply = mediametrics::Item::convert(reply2);
return status;
}
@@ -1850,7 +1850,7 @@ android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz)
}
// get what we have for the metrics from the codec
- MediaAnalyticsItem *item = 0;
+ mediametrics::Item *item = 0;
status_t err = codec->getMetrics(env, item);
if (err != OK) {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index dfe30a3f5909..ce1c805b6366 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -21,7 +21,7 @@
#include "jni.h"
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AHandler.h>
@@ -124,7 +124,7 @@ struct JMediaCodec : public AHandler {
status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const;
- status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const;
+ status_t getMetrics(JNIEnv *env, mediametrics::Item * &reply) const;
status_t setParameters(const sp<AMessage> &params);
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index f5ae9d0d5d2f..528dc62c3016 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -913,7 +913,7 @@ android_media_MediaExtractor_native_getMetrics(JNIEnv * env, jobject thiz)
}
// build and return the Bundle
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(reply);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index d3151545333c..37aca08643b2 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,11 +18,13 @@
#include <binder/Parcel.h>
#include <jni.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
+#include <variant>
#include "android_media_MediaMetricsJNI.h"
#include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
// This source file is compiled and linked into:
// core/jni/ (libandroid_runtime.so)
@@ -52,7 +54,7 @@ struct BundleHelper {
const jmethodID constructID;
jobject const bundle;
- // We use templated put to access MediaAnalyticsItem based on data type not type enum.
+ // We use templated put to access mediametrics::Item based on data type not type enum.
// See std::variant and std::visit.
template<typename T>
void put(jstring keyName, const T& value) = delete;
@@ -73,18 +75,30 @@ struct BundleHelper {
}
template<>
- void put(jstring keyName, const char * const& value) {
- env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
+ void put(jstring keyName, const std::string& value) {
+ env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value.c_str()));
}
template<>
- void put(jstring keyName, char * const& value) {
+ void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
+ ; // rate is currently ignored
+ }
+
+ template<>
+ void put(jstring keyName, const std::monostate& value) {
+ ; // none is currently ignored
+ }
+
+ // string char * helpers
+
+ template<>
+ void put(jstring keyName, const char * const& value) {
env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
}
template<>
- void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
- ; // rate is currently ignored
+ void put(jstring keyName, char * const& value) {
+ env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
}
// We allow both jstring and non-jstring variants.
@@ -97,7 +111,7 @@ struct BundleHelper {
// place the attributes into a java PersistableBundle object
jobject MediaMetricsJNI::writeMetricsToBundle(
- JNIEnv* env, MediaAnalyticsItem *item, jobject bundle)
+ JNIEnv* env, mediametrics::Item *item, jobject bundle)
{
BundleHelper bh(env, bundle);
@@ -124,6 +138,28 @@ jobject MediaMetricsJNI::writeMetricsToBundle(
return bh.bundle;
}
+// Implementation of MediaMetrics.native_submit_bytebuffer(),
+// Delivers the byte buffer to the mediametrics service.
+static jint android_media_MediaMetrics_submit_bytebuffer(
+ JNIEnv* env, jobject thiz, jobject byteBuffer, jint length)
+{
+ const jbyte* buffer =
+ reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(byteBuffer));
+ if (buffer == nullptr) {
+ ALOGE("Error retrieving source of audio data to play, can't play");
+ return (jint)BAD_VALUE;
+ }
+
+ // TODO: directly record item to MediaMetrics service.
+ mediametrics::Item item;
+ if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
+ ALOGW("%s: cannot read from byte string", __func__);
+ return (jint)BAD_VALUE;
+ }
+ item.selfrecord();
+ return (jint)NO_ERROR;
+}
+
// Helper function to convert a native PersistableBundle to a Java
// PersistableBundle.
jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
@@ -191,5 +227,18 @@ jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
return newBundle;
}
-}; // namespace android
+// ----------------------------------------------------------------------------
+
+static constexpr JNINativeMethod gMethods[] = {
+ {"native_submit_bytebuffer", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_media_MediaMetrics_submit_bytebuffer},
+};
+// Registers the native methods, called from core/jni/AndroidRuntime.cpp
+int register_android_media_MediaMetrics(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(
+ env, "android/media/MediaMetrics", gMethods, std::size(gMethods));
+}
+
+}; // namespace android
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index 63ec27aa58ee..bcad5587d909 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -19,7 +19,7 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <binder/PersistableBundle.h>
// Copeid from core/jni/ (libandroid_runtime.so)
@@ -27,7 +27,7 @@ namespace android {
class MediaMetricsJNI {
public:
- static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+ static jobject writeMetricsToBundle(JNIEnv* env, mediametrics::Item *item, jobject mybundle);
static jobject nativeToJavaPersistableBundle(JNIEnv*, os::PersistableBundle*);
};
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index b4fa07bd7420..963b650292e4 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -23,7 +23,7 @@
#include <media/AudioResamplerPublic.h>
#include <media/IMediaHTTPService.h>
#include <media/MediaPlayerInterface.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
#include <stdio.h>
#include <assert.h>
@@ -682,7 +682,7 @@ android_media_MediaPlayer_native_getMetrics(JNIEnv *env, jobject thiz)
return (jobject) NULL;
}
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(p);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index f8ba36d99de7..6eeccf0e5a77 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -29,7 +29,7 @@
#include <gui/Surface.h>
#include <camera/Camera.h>
#include <media/mediarecorder.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/MicrophoneInfo.h>
#include <media/stagefright/PersistentSurface.h>
#include <utils/threads.h>
@@ -694,7 +694,7 @@ android_media_MediaRecorder_native_getMetrics(JNIEnv *env, jobject thiz)
}
// build and return the Bundle
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(reply);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
index dfbf5d20e074..121443f56285 100644
--- a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
+++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
@@ -17,12 +17,11 @@
package android.media.effect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterFactory;
import android.filterfw.core.FilterFunction;
import android.filterfw.core.Frame;
-import android.media.effect.EffectContext;
/**
* Effect subclass for effects based on a single Filter. Subclasses need only invoke the
diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
index 52615bf09faa..3a7f1ed4f7ec 100644
--- a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
@@ -17,11 +17,11 @@
package android.filterfw;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.filterfw.core.AsyncRunner;
-import android.filterfw.core.FilterGraph;
import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterGraph;
import android.filterfw.core.FrameManager;
import android.filterfw.core.GraphRunner;
import android.filterfw.core.RoundRobinScheduler;
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index 4f56b923f6ed..a608ef5be3f4 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -17,19 +17,15 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.io.TextGraphReader;
-import android.filterfw.io.GraphIOException;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.format.ObjectFormat;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.TextGraphReader;
import android.util.Log;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
-import java.lang.Thread;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
index a19220ef85f8..6b0a2193dceb 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
@@ -17,11 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Filter;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.GLEnvironment;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
index e6ca11ffca3c..35a298fd6dfb 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
@@ -17,6 +17,11 @@
package android.filterfw.core;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.filterpacks.base.FrameBranch;
+import android.filterpacks.base.NullFilter;
+import android.util.Log;
+
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -25,14 +30,6 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.KeyValueMap;
-import android.filterpacks.base.FrameBranch;
-import android.filterpacks.base.NullFilter;
-
-import android.annotation.UnsupportedAppUsage;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java
index e880783247a3..c4d935ae4873 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Frame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java
@@ -17,9 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
index eb0ff0a32c3f..a87e9b9ffbcf 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
@@ -17,9 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.Arrays;
import java.util.Map.Entry;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
index 85c8fcd9787d..e49aaf1d6fad 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
@@ -17,10 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
index e25d6a7d70ab..7e4e8a64a81f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
@@ -17,13 +17,12 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.NativeAllocatorTag;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.SurfaceTexture;
+import android.media.MediaRecorder;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
-import android.media.MediaRecorder;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
index 9e3025fafb6e..1ccd7feaa7c3 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
@@ -17,15 +17,10 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.StopWatchMap;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.opengl.GLES20;
import android.graphics.Rect;
+import android.opengl.GLES20;
import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
index 250cfaaba9d4..b57e8bb7262e 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
@@ -17,7 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
index ae2ad99899f0..da00b1ffb180 100644
--- a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
@@ -17,9 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.Arrays;
diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java
index 376c08554eb2..145388e4437e 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Program.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Program.java
@@ -17,8 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
index f41636e7cf76..e043be0e27bd 100644
--- a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
+++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
@@ -17,12 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.NativeAllocatorTag;
-import android.filterfw.core.Program;
-import android.filterfw.core.StopWatchMap;
-import android.filterfw.core.VertexFrame;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.geometry.Quad;
import android.opengl.GLES20;
diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
index ac087305287f..0e05092d0cdd 100644
--- a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
@@ -17,7 +17,7 @@
package android.filterfw.format;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.MutableFrameFormat;
import android.graphics.Bitmap;
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
index d7acf12dd1de..96d2d7b08b74 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Point.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
@@ -17,8 +17,7 @@
package android.filterfw.geometry;
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
index 610e5b80399d..2b308a91576f 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
@@ -17,10 +17,8 @@
package android.filterfw.geometry;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.geometry.Point;
+import android.compat.annotation.UnsupportedAppUsage;
-import java.lang.Float;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 2c60d6b3dec7..326628587837 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,7 +16,15 @@
package com.android.mediaroutertest;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
+import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.media.MediaRoute2Info;
@@ -24,20 +32,37 @@ import android.media.MediaRouter2;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MediaRouter2Test {
+ private static final String TAG = "MediaRouter2Test";
Context mContext;
+ private MediaRouter2 mRouter2;
+ private Executor mExecutor;
+
+ private static final int TIMEOUT_MS = 5000;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
+ mRouter2 = MediaRouter2.getInstance(mContext);
+ mExecutor = Executors.newSingleThreadExecutor();
}
@After
@@ -50,4 +75,95 @@ public class MediaRouter2Test {
MediaRoute2Info initiallySelectedRoute = router.getSelectedRoute();
assertNotNull(initiallySelectedRoute);
}
+
+ /**
+ * Tests if we get proper routes for application that has special control category.
+ */
+ @Test
+ public void testGetRoutes() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+
+ assertEquals(1, routes.size());
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+ }
+
+ @Test
+ public void testControlVolumeWithRouter() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ assertNotNull(volRoute);
+
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+
+ awaitOnRouteChanged(
+ () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
+ ROUTE_ID_VARIABLE_VOLUME,
+ (route -> route.getVolume() == originalVolume + deltaVolume));
+
+ awaitOnRouteChanged(
+ () -> mRouter2.requestSetVolume(volRoute, originalVolume),
+ ROUTE_ID_VARIABLE_VOLUME,
+ (route -> route.getVolume() == originalVolume));
+ }
+
+
+ // Helper for getting routes easily
+ static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+ Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+ for (MediaRoute2Info route : routes) {
+ // intentionally not using route.getUniqueId() for convenience.
+ routeMap.put(route.getId(), route);
+ }
+ return routeMap;
+ }
+
+ Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories)
+ throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // A dummy callback is required to send control category info.
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ for (int i = 0; i < routes.size(); i++) {
+ //TODO: use isSystem() or similar method when it's ready
+ if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+ latch.countDown();
+ }
+ }
+ }
+ };
+
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, routerCallback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mRouter2.getRoutes());
+ } finally {
+ mRouter2.unregisterCallback(routerCallback);
+ }
+ }
+
+ void awaitOnRouteChanged(Runnable task, String routeId,
+ Predicate<MediaRoute2Info> predicate) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> changed) {
+ MediaRoute2Info route = createRouteMap(changed).get(routeId);
+ if (route != null && predicate.test(route)) {
+ latch.countDown();
+ }
+ }
+ };
+ mRouter2.registerCallback(mExecutor, callback);
+ try {
+ task.run();
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mRouter2.unregisterCallback(callback);
+ }
+ }
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index c70ad8d8755c..b380aff20334 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -64,6 +64,9 @@ public class MediaRouterManagerTest {
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+ public static final String SYSTEM_PROVIDER_ID =
+ "com.android.server.media/.SystemMediaRoute2Provider";
+
public static final int VOLUME_MAX = 100;
public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
@@ -78,10 +81,7 @@ public class MediaRouterManagerTest {
public static final String CATEGORY_SPECIAL =
"com.android.mediarouteprovider.CATEGORY_SPECIAL";
- // system routes
- private static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
- private static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
private static final int TIMEOUT_MS = 5000;
@@ -93,10 +93,9 @@ public class MediaRouterManagerTest {
private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
- private Map<String, MediaRoute2Info> mRoutes;
- private static final List<String> CATEGORIES_ALL = new ArrayList();
- private static final List<String> CATEGORIES_SPECIAL = new ArrayList();
+ public static final List<String> CATEGORIES_ALL = new ArrayList();
+ public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
static {
@@ -109,7 +108,6 @@ public class MediaRouterManagerTest {
CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
}
-
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
@@ -118,10 +116,6 @@ public class MediaRouterManagerTest {
//TODO: If we need to support thread pool executors, change this to thread pool executor.
mExecutor = Executors.newSingleThreadExecutor();
mPackageName = mContext.getPackageName();
-
- // ensure media router 2 client
- addRouterCallback(new MediaRouter2.Callback());
- mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
}
@After
@@ -168,6 +162,9 @@ public class MediaRouterManagerTest {
@Test
public void testOnRoutesRemoved() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+ addRouterCallback(new MediaRouter2.Callback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRoutesRemoved(List<MediaRoute2Info> routes) {
@@ -182,7 +179,7 @@ public class MediaRouterManagerTest {
//TODO: Figure out a more proper way to test.
// (Control requests shouldn't be used in this way.)
- mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
+ mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -198,23 +195,15 @@ public class MediaRouterManagerTest {
}
/**
- * Tests if we get proper routes for application that has special control category.
- */
- @Test
- public void testGetRoutes() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
-
- assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
- }
-
- /**
* Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
*/
@Test
public void testRouterOnRouteSelected() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
CountDownLatch latch = new CountDownLatch(1);
+ addManagerCallback(new MediaRouter2Manager.Callback());
addRouterCallback(new MediaRouter2.Callback() {
@Override
public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
@@ -224,12 +213,16 @@ public class MediaRouterManagerTest {
}
});
- MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- mManager.selectRoute(mPackageName, routeToSelect);
+ try {
+ mManager.selectRoute(mPackageName, routeToSelect);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mManager.unselectRoute(mPackageName);
+ }
}
/**
@@ -239,7 +232,9 @@ public class MediaRouterManagerTest {
@Test
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -250,12 +245,15 @@ public class MediaRouterManagerTest {
}
});
- MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- mManager.selectRoute(mPackageName, routeToSelect);
-
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ try {
+ mManager.selectRoute(mPackageName, routeToSelect);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mManager.unselectRoute(mPackageName);
+ }
}
/**
@@ -263,13 +261,16 @@ public class MediaRouterManagerTest {
*/
@Test
public void testSingleProviderSelect() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
+
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)),
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)),
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
ROUTE_ID2,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -280,27 +281,10 @@ public class MediaRouterManagerTest {
}
@Test
- public void testControlVolumeWithRouter() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
-
+ public void testControlVolumeWithManager() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
- int originalVolume = volRoute.getVolume();
- int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
-
- awaitOnRouteChanged(
- () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
- ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume + deltaVolume));
- awaitOnRouteChanged(
- () -> mRouter2.requestSetVolume(volRoute, originalVolume),
- ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume));
- }
-
- @Test
- public void testControlVolumeWithManager() throws Exception {
- MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
int originalVolume = volRoute.getVolume();
int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
@@ -317,39 +301,16 @@ public class MediaRouterManagerTest {
@Test
public void testVolumeHandling() throws Exception {
- MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME);
- MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+ MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
+ MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
- @Test
- public void testDefaultRoute() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_LIVE_AUDIO);
-
- assertNotNull(routes.get(DEFAULT_ROUTE_ID));
- }
-
- Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2.Callback callback = new MediaRouter2.Callback() {
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> added) {
- if (added.size() > 0) latch.countDown();
- }
- };
- mRouter2.setControlCategories(controlCategories);
- mRouter2.registerCallback(mExecutor, callback);
- try {
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- return createRouteMap(mRouter2.getRoutes());
- } finally {
- mRouter2.unregisterCallback(callback);
- }
- }
-
Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
throws Exception {
CountDownLatch latch = new CountDownLatch(2);
@@ -359,13 +320,17 @@ public class MediaRouterManagerTest {
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
- if (routes.size() > 0) {
- latch.countDown();
+ for (int i = 0; i < routes.size(); i++) {
+ //TODO: use isSystem() or similar method when it's ready
+ if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+ latch.countDown();
+ break;
+ }
}
}
@Override
- public void onControlCategoriesChanged(String packageName) {
+ public void onControlCategoriesChanged(String packageName, List<String> categories) {
if (TextUtils.equals(mPackageName, packageName)) {
latch.countDown();
}
@@ -375,7 +340,7 @@ public class MediaRouterManagerTest {
mRouter2.setControlCategories(controlCategories);
mRouter2.registerCallback(mExecutor, routerCallback);
try {
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
} finally {
mRouter2.unregisterCallback(routerCallback);
@@ -383,27 +348,6 @@ public class MediaRouterManagerTest {
}
}
- void awaitOnRouteChanged(Runnable task, String routeId,
- Predicate<MediaRoute2Info> predicate) throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2.Callback callback = new MediaRouter2.Callback() {
- @Override
- public void onRoutesChanged(List<MediaRoute2Info> changed) {
- MediaRoute2Info route = createRouteMap(changed).get(routeId);
- if (route != null && predicate.test(route)) {
- latch.countDown();
- }
- }
- };
- mRouter2.registerCallback(mExecutor, callback);
- try {
- task.run();
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } finally {
- mRouter2.unregisterCallback(callback);
- }
- }
-
void awaitOnRouteChangedManager(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index 728e6e18cc31..90b46fd5901a 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -18,11 +18,11 @@
package android.opengl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.SurfaceTexture;
import android.view.Surface;
-import android.view.SurfaceView;
import android.view.SurfaceHolder;
+import android.view.SurfaceView;
/**
* EGL 1.4
diff --git a/opengl/java/android/opengl/GLES20.java b/opengl/java/android/opengl/GLES20.java
index d66e7ac84a3b..e853e4447daa 100644
--- a/opengl/java/android/opengl/GLES20.java
+++ b/opengl/java/android/opengl/GLES20.java
@@ -19,7 +19,7 @@
package android.opengl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/** OpenGL ES 2.0
*/
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 8a3e6a0b0fd5..75131b0f6b9c 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -16,7 +16,7 @@
package android.opengl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Trace;
import android.util.AttributeSet;
diff --git a/opengl/java/com/google/android/gles_jni/EGLImpl.java b/opengl/java/com/google/android/gles_jni/EGLImpl.java
index f94f69f0fd3f..b4ea0a6132a5 100644
--- a/opengl/java/com/google/android/gles_jni/EGLImpl.java
+++ b/opengl/java/com/google/android/gles_jni/EGLImpl.java
@@ -16,13 +16,12 @@
package com.google.android.gles_jni;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
diff --git a/opengl/java/com/google/android/gles_jni/GLImpl.java b/opengl/java/com/google/android/gles_jni/GLImpl.java
index 2a8d07f03148..3c808a6ada48 100644
--- a/opengl/java/com/google/android/gles_jni/GLImpl.java
+++ b/opengl/java/com/google/android/gles_jni/GLImpl.java
@@ -20,14 +20,13 @@
package com.google.android.gles_jni;
import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.os.Build;
import android.os.UserHandle;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.nio.Buffer;
import javax.microedition.khronos.opengles.GL10;
diff --git a/opengl/java/javax/microedition/khronos/egl/EGL10.java b/opengl/java/javax/microedition/khronos/egl/EGL10.java
index 8a2517062d4d..ea571c7311a1 100644
--- a/opengl/java/javax/microedition/khronos/egl/EGL10.java
+++ b/opengl/java/javax/microedition/khronos/egl/EGL10.java
@@ -16,8 +16,7 @@
package javax.microedition.khronos.egl;
-import android.annotation.UnsupportedAppUsage;
-import java.lang.String;
+import android.compat.annotation.UnsupportedAppUsage;
public interface EGL10 extends EGL {
int EGL_SUCCESS = 0x3000;
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index b2451c91057c..96bcda31e549 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -53,7 +53,6 @@ android_library {
],
libs: [
- "telephony-common",
"android.car",
],
@@ -108,7 +107,6 @@ android_library {
],
libs: [
"android.test.runner",
- "telephony-common",
"android.test.base",
"android.car",
],
@@ -129,7 +127,6 @@ android_app {
],
libs: [
- "telephony-common",
"android.car",
],
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
index 1f88114becd8..bd5b79594c19 100644
--- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
@@ -16,10 +16,10 @@
package com.android.incremental.nativeadb;
-import android.service.incremental.IncrementalDataLoaderService;
+import android.service.dataloader.DataLoaderService;
/** This code is used for testing only. */
-public class NativeAdbDataLoaderService extends IncrementalDataLoaderService {
+public class NativeAdbDataLoaderService extends DataLoaderService {
public static final String TAG = "NativeAdbDataLoaderService";
static {
System.loadLibrary("nativeadbdataloaderservice_jni");
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 14f233d958e6..2c001b033311 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,5 @@
package com.android.settingslib;
-import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
-
import android.annotation.ColorInt;
import android.content.Context;
import android.content.Intent;
@@ -25,6 +23,8 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import com.android.internal.annotations.VisibleForTesting;
@@ -412,15 +412,30 @@ public class Utils {
// service" or "emergency calls only" text that indicates that voice
// is not available. Note that we ignore the IWLAN service state
// because that state indicates the use of VoWIFI and not cell service
- int state = serviceState.getState();
- int dataState = serviceState.getDataRegState();
+ final int state = serviceState.getState();
+ final int dataState = serviceState.getDataRegState();
+
if (state == ServiceState.STATE_OUT_OF_SERVICE
|| state == ServiceState.STATE_EMERGENCY_ONLY) {
- if (dataState == ServiceState.STATE_IN_SERVICE
- && serviceState.getDataNetworkType() != RIL_RADIO_TECHNOLOGY_IWLAN) {
+ if (dataState == ServiceState.STATE_IN_SERVICE && isNotInIwlan(serviceState)) {
return ServiceState.STATE_IN_SERVICE;
}
}
return state;
}
+
+ private static boolean isNotInIwlan(ServiceState serviceState) {
+ final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+ if (networkRegWlan == null) {
+ return true;
+ }
+
+ final boolean isInIwlan = (networkRegWlan.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ || (networkRegWlan.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+ return !isInIwlan;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 443543b89a66..b13c483fc2c2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -36,7 +36,6 @@ import android.net.ScoredNetwork;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -181,9 +180,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
static final String KEY_CONFIG = "key_config";
static final String KEY_FQDN = "key_fqdn";
static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
- static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
- static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
- static final String KEY_CARRIER_NAME = "key_carrier_name";
static final String KEY_EAPTYPE = "eap_psktype";
static final String KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS =
"key_subscription_expiration_time_in_millis";
@@ -265,8 +261,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
@PasspointConfigurationVersion private int mPasspointConfigurationVersion =
PasspointConfigurationVersion.INVALID;
- private boolean mIsCarrierAp = false;
-
private OsuProvider mOsuProvider;
private String mOsuStatus;
@@ -276,12 +270,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
private boolean mIsPskSaeTransitionMode = false;
private boolean mIsOweTransitionMode = false;
- /**
- * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
- */
- private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
- private String mCarrierName = null;
-
public AccessPoint(Context context, Bundle savedState) {
mContext = context;
@@ -330,15 +318,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
}
- if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
- mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
- }
- if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
- mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
- }
- if (savedState.containsKey(KEY_CARRIER_NAME)) {
- mCarrierName = savedState.getString(KEY_CARRIER_NAME);
- }
if (savedState.containsKey(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS)) {
mSubscriptionExpirationTimeInMillis =
savedState.getLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS);
@@ -966,10 +945,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult);
mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult);
-
- mIsCarrierAp = bestResult.isCarrierAp;
- mCarrierApEapType = bestResult.carrierApEapType;
- mCarrierName = bestResult.carrierName;
}
// Update the config SSID of a Passpoint network to that of the best RSSI
if (isPasspoint()) {
@@ -1094,18 +1069,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
return null;
}
- public boolean isCarrierAp() {
- return mIsCarrierAp;
- }
-
- public int getCarrierApEapType() {
- return mCarrierApEapType;
- }
-
- public String getCarrierName() {
- return mCarrierName;
- }
-
public String getSavedNetworkSummary() {
WifiConfiguration config = mConfig;
if (config != null) {
@@ -1181,15 +1144,9 @@ public class AccessPoint implements Comparable<AccessPoint> {
summary.append(mContext.getString(R.string.tap_to_sign_up));
}
} else if (isActive()) {
- if (getDetailedState() == DetailedState.CONNECTED && mIsCarrierAp) {
- // This is the active connection on a carrier AP
- summary.append(String.format(mContext.getString(R.string.connected_via_carrier),
- mCarrierName));
- } else {
- summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
- mInfo != null && mInfo.isEphemeral(),
- mInfo != null ? mInfo.getAppPackageName() : null));
- }
+ summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
+ mInfo != null && mInfo.isEphemeral(),
+ mInfo != null ? mInfo.getAppPackageName() : null));
} else { // not active
if (mConfig != null && mConfig.hasNoInternetAccess()) {
int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
@@ -1213,9 +1170,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
summary.append(mContext.getString(R.string.wifi_disabled_generic));
break;
}
- } else if (mIsCarrierAp) {
- summary.append(String.format(mContext.getString(
- R.string.available_via_carrier), mCarrierName));
} else if (!isReachable()) { // Wifi out of range
summary.append(mContext.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
@@ -1427,9 +1381,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (mProviderFriendlyName != null) {
savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
}
- savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
- savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
- savedState.putString(KEY_CARRIER_NAME, mCarrierName);
savedState.putLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS,
mSubscriptionExpirationTimeInMillis);
savedState.putInt(KEY_PASSPOINT_CONFIGURATION_VERSION, mPasspointConfigurationVersion);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 17a73acb9bda..4a4f0e66cfe8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -56,8 +56,6 @@ public class TestAccessPointBuilder {
private int mSecurity = AccessPoint.SECURITY_NONE;
private WifiConfiguration mWifiConfig;
private WifiInfo mWifiInfo;
- private boolean mIsCarrierAp = false;
- private String mCarrierName = null;
Context mContext;
private ArrayList<ScanResult> mScanResults;
@@ -99,10 +97,6 @@ public class TestAccessPointBuilder {
}
bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
- bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
- if (mCarrierName != null) {
- bundle.putString(AccessPoint.KEY_CARRIER_NAME, mCarrierName);
- }
AccessPoint ap = new AccessPoint(mContext, bundle);
ap.setRssi(mRssi);
@@ -241,16 +235,6 @@ public class TestAccessPointBuilder {
return this;
}
- public TestAccessPointBuilder setIsCarrierAp(boolean isCarrierAp) {
- mIsCarrierAp = isCarrierAp;
- return this;
- }
-
- public TestAccessPointBuilder setCarrierName(String carrierName) {
- mCarrierName = carrierName;
- return this;
- }
-
public TestAccessPointBuilder setScoredNetworkCache(
ArrayList<TimestampedScoredNetwork> scoredNetworkCache) {
mScoredNetworkCache = scoredNetworkCache;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 3818057e8535..61cbbd3eb0a4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -40,7 +41,6 @@ import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -473,51 +473,6 @@ public class AccessPointTest {
}
@Test
- public void testSummaryString_showsAvaiableViaCarrier() {
- String carrierName = "Test Carrier";
- ScanResult result = new ScanResult();
- result.BSSID = "00:11:22:33:44:55";
- result.capabilities = "EAP";
- result.isCarrierAp = true;
- result.carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
- result.carrierName = carrierName;
-
- AccessPoint ap = new AccessPoint(mContext, Collections.singletonList(result));
- assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
- R.string.available_via_carrier), carrierName));
- assertThat(ap.isCarrierAp()).isEqualTo(true);
- assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.SIM);
- assertThat(ap.getCarrierName()).isEqualTo(carrierName);
- }
-
- @Test
- public void testSummaryString_showsConnectedViaCarrier() {
- int networkId = 123;
- int rssi = -55;
- String carrierName = "Test Carrier";
- WifiConfiguration config = new WifiConfiguration();
- config.networkId = networkId;
- WifiInfo wifiInfo = new WifiInfo();
- wifiInfo.setNetworkId(networkId);
- wifiInfo.setRssi(rssi);
-
- NetworkInfo networkInfo =
- new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
- networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
-
- AccessPoint ap = new TestAccessPointBuilder(mContext)
- .setNetworkInfo(networkInfo)
- .setNetworkId(networkId)
- .setRssi(rssi)
- .setWifiInfo(wifiInfo)
- .setIsCarrierAp(true)
- .setCarrierName(carrierName)
- .build();
- assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
- R.string.connected_via_carrier), carrierName));
- }
-
- @Test
public void testSummaryString_showsDisconnected() {
AccessPoint ap = createAccessPointWithScanResultCache();
ap.update(new WifiConfiguration());
@@ -580,31 +535,6 @@ public class AccessPointTest {
assertThat(ap.getSummary()).isEqualTo("Connected via Test App");
}
- @Test
- public void testSetScanResultWithCarrierInfo() {
- String ssid = "ssid";
- AccessPoint ap = new TestAccessPointBuilder(mContext).setSsid(ssid).build();
- assertThat(ap.isCarrierAp()).isEqualTo(false);
- assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.NONE);
- assertThat(ap.getCarrierName()).isEqualTo(null);
-
- int carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
- String carrierName = "Test Carrier";
- ScanResult scanResult = new ScanResult();
- scanResult.SSID = ssid;
- scanResult.BSSID = "00:11:22:33:44:55";
- scanResult.capabilities = "";
- scanResult.isCarrierAp = true;
- scanResult.carrierApEapType = carrierApEapType;
- scanResult.carrierName = carrierName;
-
-
- ap.setScanResults(Collections.singletonList(scanResult));
- assertThat(ap.isCarrierAp()).isEqualTo(true);
- assertThat(ap.getCarrierApEapType()).isEqualTo(carrierApEapType);
- assertThat(ap.getCarrierName()).isEqualTo(carrierName);
- }
-
private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve);
}
@@ -1435,8 +1365,15 @@ public class AccessPointTest {
*/
@Test
public void testOsuAccessPointSummary_showsProvisioningUpdates() {
- AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(),
+ OsuProvider provider = createOsuProvider();
+ Context spyContext = spy(new ContextWrapper(mContext));
+ AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider,
mScanResults);
+ Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+ osuProviderConfigMap.put(provider, null);
+ when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders(
+ Collections.singleton(provider))).thenReturn(osuProviderConfigMap);
osuAccessPoint.setListener(mMockAccessPointListener);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index fc69b1a657d4..f18ffe1ebdd4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -34,6 +34,8 @@ import android.media.AudioManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.text.TextUtils;
@@ -69,6 +71,8 @@ public class UtilsTest {
private LocationManager mLocationManager;
@Mock
private ServiceState mServiceState;
+ @Mock
+ private NetworkRegistrationInfo mNetworkRegistrationInfo;
@Before
public void setUp() {
@@ -207,6 +211,7 @@ public class UtilsTest {
@Test
public void isInService_voiceInService_returnTrue() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@@ -214,15 +219,23 @@ public class UtilsTest {
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@Test
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataNetworkType())
- .thenReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -230,12 +243,14 @@ public class UtilsTest {
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@Test
public void isInService_ServiceStatePowerOff_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -248,6 +263,7 @@ public class UtilsTest {
@Test
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_POWER_OFF);
}
@@ -255,6 +271,7 @@ public class UtilsTest {
@Test
public void getCombinedServiceState_voiceInService_returnInService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
}
@@ -263,14 +280,33 @@ public class UtilsTest {
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
}
@Test
+ public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
+ when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+ assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
+ ServiceState.STATE_OUT_OF_SERVICE);
+ }
+
+ @Test
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_OUT_OF_SERVICE);
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 76e7ab4c321a..22d843b1dc90 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -142,6 +142,8 @@ public class SecureSettings {
Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
Settings.Secure.SKIP_DIRECTION,
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
Settings.Secure.NAVIGATION_MODE,
Settings.Secure.SKIP_GESTURE_COUNT,
Settings.Secure.SKIP_TOUCH_COUNT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index ae07598bf7ad..4b10557e34d9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -216,6 +216,10 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
VALIDATORS.put(
Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+ VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
+ VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.AWARE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SKIP_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SKIP_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 80077c802def..016896f52a75 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2191,6 +2191,14 @@ class SettingsProtoDumpUtil {
Settings.Secure.NAVIGATION_MODE,
SecureSettingsProto.NAVIGATION_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_LEFT);
+
+ dumpSetting(s, p,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+ SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_RIGHT);
+
final long nfcPaymentToken = p.start(SecureSettingsProto.NFC_PAYMENT);
dumpSetting(s, p,
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5d0db01cd5ae..19ff2444e6ca 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -215,7 +215,6 @@ public class SettingsBackupTest {
Settings.Global.DEFAULT_DNS_SERVER,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
- Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
Settings.Global.DESK_DOCK_SOUND,
Settings.Global.DESK_UNDOCK_SOUND,
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b89f1412c6a6..51bf441fe119 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -224,6 +224,9 @@
<!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
+ <!-- Permission required for CTS test - TetheringManagerTest -->
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
index 2d37b4c9c342..15fd1f7f4957 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -101,7 +101,7 @@ public class RingtoneOverlayService extends Service {
}
private Uri scanFile(@NonNull final File file) {
- return MediaStore.scanFile(this, file);
+ return MediaStore.scanFile(getContentResolver(), file);
}
private void set(@NonNull final String name, @NonNull final Uri uri) {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 404e791291d8..0d5ede4fa5d7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -80,7 +80,7 @@ android_library {
filegroup {
name: "SystemUI-tests-utils",
srcs: [
- "tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java",
+ "tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java",
"tests/src/com/android/systemui/statusbar/RankingBuilder.java",
"tests/src/com/android/systemui/statusbar/SbnBuilder.java",
],
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bc808b3cc531..45318fde4f0e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -914,8 +914,8 @@
<string name="quick_settings_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
<!-- QuickSettings: Label for the toggle to activate dark theme (A.K.A Dark Mode). [CHAR LIMIT=20] -->
<string name="quick_settings_ui_mode_night_label">Dark theme</string>
- <!-- QuickSettings: Label for the dark theme tile when enabled by battery saver. [CHAR LIMIT=40] -->
- <string name="quick_settings_ui_mode_night_label_battery_saver">Dark theme\nBattery saver</string>
+ <!-- QuickSettings: Secondary text for the dark theme tile when enabled by battery saver. [CHAR LIMIT=20] -->
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery Saver</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 5d084e7f3272..c2cedad0dc48 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -30,6 +30,7 @@ import android.os.UserHandle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
@@ -114,11 +115,7 @@ public class EmergencyButton extends Button {
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- takeEmergencyCallAction();
- }
- });
+ setOnClickListener(v -> takeEmergencyCallAction());
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
@@ -168,9 +165,9 @@ public class EmergencyButton extends Button {
*/
public void takeEmergencyCallAction() {
MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
- // TODO: implement a shorter timeout once new PowerManager API is ready.
- // should be the equivalent to the old userActivity(EMERGENCY_CALL_TIMEOUT)
- mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ if (mPowerManager != null) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ }
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
@@ -182,10 +179,19 @@ public class EmergencyButton extends Button {
mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
}
} else {
- Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
- true /* bypassHandler */);
+ KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ if (updateMonitor != null) {
+ updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+ } else {
+ Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
+ }
+ TelecomManager telecomManager = getTelecommManager();
+ if (telecomManager == null) {
+ Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ return;
+ }
Intent emergencyDialIntent =
- getTelecommManager().createLaunchEmergencyDialerIntent(null /* number*/)
+ telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
index 8e472f9c98a3..8503396975b5 100644
--- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -57,6 +57,7 @@ public class CornerHandleView extends View {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(getStrokePx());
+ setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
final int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
final int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
@@ -114,10 +115,17 @@ public class CornerHandleView extends View {
* appropriately. Intention is to match the home handle color.
*/
public void updateDarkness(float darkIntensity) {
+ // Handle color is same as home handle color.
int color = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
mLightColor, mDarkColor);
+ // Shadow color is inverse of handle color.
+ int shadowColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ mDarkColor, mLightColor);
if (mPaint.getColor() != color) {
mPaint.setColor(color);
+ mPaint.setShadowLayer(/** radius */ getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.assist_handle_shadow_radius), /** shadowDx */ 0,
+ /** shadowDy */ 0, /** color */ shadowColor);
if (getVisibility() == VISIBLE && getAlpha() > 0) {
invalidate();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index a6a3ce06324f..dc2499602125 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -16,22 +16,15 @@
package com.android.systemui.bubbles;
import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
-import android.content.pm.LauncherApps;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.PathParser;
import android.widget.ImageView;
-import com.android.internal.graphics.ColorUtils;
-import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.DotRenderer;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -46,9 +39,9 @@ import com.android.systemui.R;
public class BadgedImageView extends ImageView {
/** Same value as Launcher3 dot code */
- private static final float WHITE_SCRIM_ALPHA = 0.54f;
+ public static final float WHITE_SCRIM_ALPHA = 0.54f;
/** Same as value in Launcher3 IconShape */
- private static final int DEFAULT_PATH_SIZE = 100;
+ public static final int DEFAULT_PATH_SIZE = 100;
static final int DOT_STATE_DEFAULT = 0;
static final int DOT_STATE_SUPPRESSED_FOR_FLYOUT = 1;
@@ -58,7 +51,6 @@ public class BadgedImageView extends ImageView {
private int mCurrentDotState = DOT_STATE_SUPPRESSED_FOR_FLYOUT;
private Bubble mBubble;
- private BubbleIconFactory mBubbleIconFactory;
private int mIconBitmapSize;
private DotRenderer mDotRenderer;
@@ -94,6 +86,18 @@ public class BadgedImageView extends ImageView {
mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
}
+ /**
+ * Updates the view with provided info.
+ */
+ public void update(Bubble bubble, Bitmap bubbleImage, int dotColor, Path dotPath) {
+ mBubble = bubble;
+ setImageBitmap(bubbleImage);
+ setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
+ mDotColor = dotColor;
+ drawDot(dotPath);
+ animateDot();
+ }
+
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -140,14 +144,6 @@ public class BadgedImageView extends ImageView {
}
/**
- * The colour to use for the dot.
- */
- void setDotColor(int color) {
- mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
- invalidate();
- }
-
- /**
* @param iconPath The new icon path to use when calculating dot position.
*/
void drawDot(Path iconPath) {
@@ -187,25 +183,6 @@ public class BadgedImageView extends ImageView {
}
/**
- * Populates this view with a bubble.
- * <p>
- * This should only be called when a new bubble is being set on the view, updates to the
- * current bubble should use {@link #update(Bubble)}.
- *
- * @param bubble the bubble to display in this view.
- */
- public void setBubble(Bubble bubble) {
- mBubble = bubble;
- }
-
- /**
- * @param factory Factory for creating normalized bubble icons.
- */
- public void setBubbleIconFactory(BubbleIconFactory factory) {
- mBubbleIconFactory = factory;
- }
-
- /**
* The key for the {@link Bubble} associated with this view, if one exists.
*/
@Nullable
@@ -213,15 +190,6 @@ public class BadgedImageView extends ImageView {
return (mBubble != null) ? mBubble.getKey() : null;
}
- /**
- * Updates the UI based on the bubble, updates badge and animates messages as needed.
- */
- public void update(Bubble bubble) {
- mBubble = bubble;
- setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
- updateViews();
- }
-
int getDotColor() {
return mDotColor;
}
@@ -277,47 +245,4 @@ public class BadgedImageView extends ImageView {
}
}).start();
}
-
- void updateViews() {
- if (mBubble == null || mBubbleIconFactory == null) {
- return;
- }
-
- Drawable bubbleDrawable = getBubbleDrawable(mContext);
- BitmapInfo badgeBitmapInfo = mBubbleIconFactory.getBadgedBitmap(mBubble);
- BitmapInfo bubbleBitmapInfo = mBubbleIconFactory.getBubbleBitmap(bubbleDrawable,
- badgeBitmapInfo);
- setImageBitmap(bubbleBitmapInfo.icon);
-
- // Update badge.
- mDotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, Color.WHITE, WHITE_SCRIM_ALPHA);
- setDotColor(mDotColor);
-
- // Update dot.
- Path iconPath = PathParser.createPathFromPathData(
- getResources().getString(com.android.internal.R.string.config_icon_mask));
- Matrix matrix = new Matrix();
- float scale = mBubbleIconFactory.getNormalizer().getScale(bubbleDrawable,
- null /* outBounds */, null /* path */, null /* outMaskShape */);
- float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
- matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
- radius /* pivot y */);
- iconPath.transform(matrix);
- drawDot(iconPath);
-
- animateDot();
- }
-
- Drawable getBubbleDrawable(Context context) {
- if (mBubble.getShortcutInfo() != null && mBubble.usingShortcutInfo()) {
- LauncherApps launcherApps =
- (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
- int density = getContext().getResources().getConfiguration().densityDpi;
- return launcherApps.getShortcutIconDrawable(mBubble.getShortcutInfo(), density);
- } else {
- Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
- Icon ic = metadata.getIcon();
- return ic.loadDrawable(context);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 7934e10c8605..77c8e0b07158 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static android.os.AsyncTask.Status.FINISHED;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -26,20 +27,17 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-import android.view.LayoutInflater;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -59,19 +57,19 @@ class Bubble {
private NotificationEntry mEntry;
private final String mKey;
private final String mGroupId;
+
+ private long mLastUpdated;
+ private long mLastAccessed;
+
+ // Items that are typically loaded later
private String mAppName;
- private Drawable mUserBadgedAppIcon;
private ShortcutInfo mShortcutInfo;
-
- private boolean mInflated;
private BadgedImageView mIconView;
private BubbleExpandedView mExpandedView;
- private BubbleIconFactory mBubbleIconFactory;
-
- private long mLastUpdated;
- private long mLastAccessed;
- private boolean mIsUserCreated;
+ private boolean mInflated;
+ private BubbleViewInfoTask mInflationTask;
+ private boolean mInflateSynchronously;
/**
* Whether this notification should be shown in the shade when it is also displayed as a bubble.
@@ -94,37 +92,11 @@ class Bubble {
/** Used in tests when no UI is required. */
@VisibleForTesting(visibility = PRIVATE)
- Bubble(Context context, NotificationEntry e) {
+ Bubble(NotificationEntry e) {
mEntry = e;
mKey = e.getKey();
mLastUpdated = e.getSbn().getPostTime();
mGroupId = groupId(e);
-
- String shortcutId = e.getSbn().getNotification().getShortcutId();
- if (BubbleExperimentConfig.useShortcutInfoToBubble(context)
- && shortcutId != null) {
- mShortcutInfo = BubbleExperimentConfig.getShortcutInfo(context,
- e.getSbn().getPackageName(),
- e.getSbn().getUser(), shortcutId);
- }
-
- PackageManager pm = context.getPackageManager();
- ApplicationInfo info;
- try {
- info = pm.getApplicationInfo(
- mEntry.getSbn().getPackageName(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- mAppName = String.valueOf(pm.getApplicationLabel(info));
- }
- Drawable appIcon = pm.getApplicationIcon(mEntry.getSbn().getPackageName());
- mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.getSbn().getUser());
- } catch (PackageManager.NameNotFoundException unused) {
- mAppName = mEntry.getSbn().getPackageName();
- }
}
public String getKey() {
@@ -143,41 +115,22 @@ class Bubble {
return mEntry.getSbn().getPackageName();
}
+ @Nullable
public String getAppName() {
return mAppName;
}
- Drawable getUserBadgedAppIcon() {
- return mUserBadgedAppIcon;
- }
-
@Nullable
public ShortcutInfo getShortcutInfo() {
return mShortcutInfo;
}
- /**
- * Whether shortcut information should be used to populate the bubble.
- * <p>
- * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}.
- * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
- */
- public boolean usingShortcutInfo() {
- return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
- }
-
- void setBubbleIconFactory(BubbleIconFactory factory) {
- mBubbleIconFactory = factory;
- }
-
- boolean isInflated() {
- return mInflated;
- }
-
+ @Nullable
BadgedImageView getIconView() {
return mIconView;
}
+ @Nullable
BubbleExpandedView getExpandedView() {
return mExpandedView;
}
@@ -188,20 +141,62 @@ class Bubble {
}
}
- void inflate(LayoutInflater inflater, BubbleStackView stackView) {
- if (mInflated) {
- return;
+ /**
+ * Sets whether to perform inflation on the same thread as the caller. This method should only
+ * be used in tests, not in production.
+ */
+ @VisibleForTesting
+ void setInflateSynchronously(boolean inflateSynchronously) {
+ mInflateSynchronously = inflateSynchronously;
+ }
+
+ /**
+ * Starts a task to inflate & load any necessary information to display a bubble.
+ *
+ * @param callback the callback to notify one the bubble is ready to be displayed.
+ * @param context the context for the bubble.
+ * @param stackView the stackView the bubble is eventually added to.
+ * @param iconFactory the iconfactory use to create badged images for the bubble.
+ */
+ void inflate(BubbleViewInfoTask.Callback callback,
+ Context context,
+ BubbleStackView stackView,
+ BubbleIconFactory iconFactory) {
+ if (isBubbleLoading()) {
+ mInflationTask.cancel(true /* mayInterruptIfRunning */);
+ }
+ mInflationTask = new BubbleViewInfoTask(this,
+ context,
+ stackView,
+ iconFactory,
+ callback);
+ if (mInflateSynchronously) {
+ mInflationTask.onPostExecute(mInflationTask.doInBackground());
+ } else {
+ mInflationTask.execute();
}
- mIconView = (BadgedImageView) inflater.inflate(
- R.layout.bubble_view, stackView, false /* attachToRoot */);
- mIconView.setBubbleIconFactory(mBubbleIconFactory);
- mIconView.setBubble(this);
+ }
- mExpandedView = (BubbleExpandedView) inflater.inflate(
- R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- mExpandedView.setBubble(this, stackView);
+ private boolean isBubbleLoading() {
+ return mInflationTask != null && mInflationTask.getStatus() != FINISHED;
+ }
- mInflated = true;
+ boolean isInflated() {
+ return mInflated;
+ }
+
+ void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) {
+ if (!isInflated()) {
+ mIconView = info.imageView;
+ mExpandedView = info.expandedView;
+ mInflated = true;
+ }
+
+ mShortcutInfo = info.shortcutInfo;
+ mAppName = info.appName;
+
+ mExpandedView.update(this);
+ mIconView.update(this, info.badgedBubbleImage, info.dotColor, info.dotPath);
}
/**
@@ -218,13 +213,12 @@ class Bubble {
}
}
- void updateEntry(NotificationEntry entry) {
+ /**
+ * Sets the entry associated with this bubble.
+ */
+ void setEntry(NotificationEntry entry) {
mEntry = entry;
mLastUpdated = entry.getSbn().getPostTime();
- if (mInflated) {
- mIconView.update(this);
- mExpandedView.update(this);
- }
}
/**
@@ -242,13 +236,6 @@ class Bubble {
}
/**
- * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
- */
- long getLastAccessTime() {
- return mLastAccessed;
- }
-
- /**
* @return the display id of the virtual display on which bubble contents is drawn.
*/
int getDisplayId() {
@@ -352,6 +339,16 @@ class Bubble {
}
}
+ /**
+ * Whether shortcut information should be used to populate the bubble.
+ * <p>
+ * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}.
+ * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
+ */
+ boolean usingShortcutInfo() {
+ return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
+ }
+
@Nullable
PendingIntent getBubbleIntent() {
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 1277736a798b..7cd29ea48199 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -103,7 +103,6 @@ import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -151,6 +150,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
+ private BubbleIconFactory mBubbleIconFactory;
// Tracks the id of the current (foreground) user.
private int mCurrentUserId;
@@ -184,6 +184,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
/** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private boolean mInflateSynchronously;
+
/**
* Listener to be notified when some states of the bubbles change.
*/
@@ -353,6 +355,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mUserBlockedBubbles = new HashSet<>();
mScreenshotHelper = new ScreenshotHelper(context);
+ mBubbleIconFactory = new BubbleIconFactory(context);
+ }
+
+ /**
+ * Sets whether to perform inflation on the same thread as the caller. This method should only
+ * be used in tests, not in production.
+ */
+ @VisibleForTesting
+ void setInflateSynchronously(boolean inflateSynchronously) {
+ mInflateSynchronously = inflateSynchronously;
}
/**
@@ -416,16 +428,23 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onUiModeChanged() {
- if (mStackView != null) {
- mStackView.onThemeChanged();
- }
+ updateForThemeChanges();
}
@Override
public void onOverlayChanged() {
+ updateForThemeChanges();
+ }
+
+ private void updateForThemeChanges() {
if (mStackView != null) {
mStackView.onThemeChanged();
}
+ mBubbleIconFactory = new BubbleIconFactory(mContext);
+ for (Bubble b: mBubbleData.getBubbles()) {
+ // Reload each bubble
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
+ }
}
@Override
@@ -509,14 +528,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
}
- void selectBubble(Bubble bubble) {
- mBubbleData.setSelectedBubble(bubble);
- }
-
@VisibleForTesting
void selectBubble(String key) {
Bubble bubble = mBubbleData.getBubbleWithKey(key);
- selectBubble(bubble);
+ mBubbleData.setSelectedBubble(bubble);
}
/**
@@ -563,11 +578,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
+ if (mStackView == null) {
+ // Lazy init stack view when a bubble is created
+ ensureStackViewCreated();
+ }
// If this is an interruptive notif, mark that it's interrupted
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
}
- mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade);
+ Bubble bubble = mBubbleData.getOrCreateBubble(notif);
+ bubble.setInflateSynchronously(mInflateSynchronously);
+ bubble.inflate(
+ b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+ mContext, mStackView, mBubbleIconFactory);
}
/**
@@ -735,7 +758,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@SuppressWarnings("FieldCanBeLocal")
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationEntry entry) {
+ public void onNotificationAdded(NotificationEntry entry) {
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
@@ -784,16 +807,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void applyUpdate(BubbleData.Update update) {
- if (mStackView == null && update.addedBubble != null) {
- // Lazy init stack view when the first bubble is added.
- ensureStackViewCreated();
- }
-
- // If not yet initialized, ignore all other changes.
- if (mStackView == null) {
- return;
- }
-
if (update.addedBubble != null) {
mStackView.addBubble(update.addedBubble);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index b7df5baa4cf1..97224f1234dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -33,6 +33,7 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController.DismissReason;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -48,7 +49,6 @@ import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Singleton;
-import com.android.systemui.R;
/**
* Keeps track of active bubbles.
@@ -180,28 +180,44 @@ public class BubbleData {
dispatchPendingChanges();
}
- void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout,
- boolean showInShade) {
+ /**
+ * Constructs a new bubble or returns an existing one. Does not add new bubbles to
+ * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
+ * for that.
+ */
+ Bubble getOrCreateBubble(NotificationEntry entry) {
+ Bubble bubble = getBubbleWithKey(entry.getKey());
+ if (bubble == null) {
+ bubble = new Bubble(entry);
+ } else {
+ bubble.setEntry(entry);
+ }
+ return bubble;
+ }
+
+ /**
+ * When this method is called it is expected that all info in the bubble has completed loading.
+ * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
+ * BubbleStackView, BubbleIconFactory).
+ */
+ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + entry);
+ Log.d(TAG, "notificationEntryUpdated: " + bubble);
}
- Bubble bubble = getBubbleWithKey(entry.getKey());
- suppressFlyout |= !shouldShowFlyout(entry);
+ Bubble prevBubble = getBubbleWithKey(bubble.getKey());
+ suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
- if (bubble == null) {
+ if (prevBubble == null) {
// Create a new bubble
- bubble = new Bubble(mContext, entry);
bubble.setSuppressFlyout(suppressFlyout);
doAdd(bubble);
trim();
} else {
// Updates an existing bubble
- bubble.updateEntry(entry);
bubble.setSuppressFlyout(suppressFlyout);
doUpdate(bubble);
}
-
if (bubble.shouldAutoExpand()) {
setSelectedBubbleInternal(bubble);
if (!mExpanded) {
@@ -214,6 +230,7 @@ public class BubbleData {
bubble.setShowInShade(!isBubbleExpandedAndSelected && showInShade);
bubble.setShowDot(!isBubbleExpandedAndSelected /* show */, true /* animate */);
dispatchPendingChanges();
+
}
public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 63d036d6362d..c1705dbb4e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -99,7 +99,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
private int mExpandedViewTouchSlop;
private Bubble mBubble;
- private String mAppName;
private BubbleController mBubbleController = Dependency.get(BubbleController.class);
private WindowManager mWindowManager;
@@ -339,68 +338,51 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
}
- /**
- * Sets the bubble used to populate this view.
- */
- public void setBubble(Bubble bubble, BubbleStackView stackView) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null"));
- }
+ void setStackView(BubbleStackView stackView) {
mStackView = stackView;
- mBubble = bubble;
- mAppName = bubble.getAppName();
-
- applyThemeAttrs();
- showSettingsIcon();
- updateExpandedView();
- }
-
- /**
- * Lets activity view know it should be shown / populated.
- */
- public void populateExpandedView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "populateExpandedView: "
- + "bubble=" + getBubbleKey());
- }
-
- if (usingActivityView()) {
- mActivityView.setCallback(mStateCallback);
- } else {
- Log.e(TAG, "Cannot populate expanded view.");
- }
}
/**
- * Updates the bubble backing this view. This will not re-populate ActivityView, it will
- * only update the deep-links in the title, and the height of the view.
+ * Sets the bubble used to populate this view.
*/
- public void update(Bubble bubble) {
+ void update(Bubble bubble) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
}
- if (bubble.getKey().equals(mBubble.getKey())) {
+ boolean isNew = mBubble == null;
+ if (isNew || bubble.getKey().equals(mBubble.getKey())) {
mBubble = bubble;
- updateSettingsContentDescription();
- updateHeight();
+ mSettingsIcon.setContentDescription(getResources().getString(
+ R.string.bubbles_settings_button_description, bubble.getAppName()));
+
+ if (isNew) {
+ mBubbleIntent = mBubble.getBubbleIntent();
+ if (mBubbleIntent != null) {
+ setContentVisibility(false);
+ mActivityView.setVisibility(VISIBLE);
+ }
+ }
+ applyThemeAttrs();
} else {
Log.w(TAG, "Trying to update entry with different key, new bubble: "
+ bubble.getKey() + " old bubble: " + bubble.getKey());
}
}
- private void updateExpandedView() {
+ /**
+ * Lets activity view know it should be shown / populated with activity content.
+ */
+ void populateExpandedView() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateExpandedView: bubble="
- + getBubbleKey());
+ Log.d(TAG, "populateExpandedView: "
+ + "bubble=" + getBubbleKey());
}
- mBubbleIntent = mBubble.getBubbleIntent();
- if (mBubbleIntent != null) {
- setContentVisibility(false);
- mActivityView.setVisibility(VISIBLE);
+ if (usingActivityView()) {
+ mActivityView.setCallback(mStateCallback);
+ } else {
+ Log.e(TAG, "Cannot populate expanded view.");
}
- updateView();
}
boolean performBackPressIfNeeded() {
@@ -490,16 +472,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
}
- private void updateSettingsContentDescription() {
- mSettingsIcon.setContentDescription(getResources().getString(
- R.string.bubbles_settings_button_description, mAppName));
- }
-
- void showSettingsIcon() {
- updateSettingsContentDescription();
- mSettingsIcon.setVisibility(VISIBLE);
- }
-
/**
* Update appearance of the expanded view being displayed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 21471ec8182b..4c1cf49ecf9e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -32,6 +32,9 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
@@ -39,8 +42,11 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
import java.util.ArrayList;
import java.util.Arrays;
@@ -227,15 +233,29 @@ public class BubbleExperimentConfig {
List<Person> personList = getPeopleFromNotification(entry);
if (personList.size() > 0) {
final Person person = personList.get(0);
-
if (person != null) {
icon = person.getIcon();
+ if (icon == null) {
+ // Lets try and grab the icon constructed by the layout
+ Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
+ if (d instanceof BitmapDrawable) {
+ icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+ }
+ }
}
}
if (icon == null) {
- icon = notification.getLargeIcon() != null
- ? notification.getLargeIcon()
- : notification.getSmallIcon();
+ boolean shouldTint = notification.getLargeIcon() == null;
+ icon = shouldTint
+ ? notification.getSmallIcon()
+ : notification.getLargeIcon();
+ if (shouldTint) {
+ int notifColor = entry.getSbn().getNotification().color;
+ notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
+ notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
+ true /* findFg */, 3f);
+ icon.setTint(notifColor);
+ }
}
if (intent != null) {
return new Notification.BubbleMetadata.Builder()
@@ -285,7 +305,7 @@ public class BubbleExperimentConfig {
}
static boolean isShortcutIntent(PendingIntent intent) {
- return intent.equals(sDummyShortcutIntent);
+ return intent != null && intent.equals(sDummyShortcutIntent);
}
static List<Person> getPeopleFromNotification(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index 9ff033cb3411..b32dbb724258 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -15,11 +15,14 @@
*/
package com.android.systemui.bubbles;
+import android.app.Notification;
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
@@ -42,19 +45,40 @@ public class BubbleIconFactory extends BaseIconFactory {
return mContext.getResources().getDimensionPixelSize(
com.android.launcher3.icons.R.dimen.profile_badge_size);
}
+ /**
+ * Returns the drawable that the developer has provided to display in the bubble.
+ */
+ Drawable getBubbleDrawable(Bubble b, Context context) {
+ if (b.getShortcutInfo() != null && b.usingShortcutInfo()) {
+ LauncherApps launcherApps =
+ (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ int density = context.getResources().getConfiguration().densityDpi;
+ return launcherApps.getShortcutIconDrawable(b.getShortcutInfo(), density);
+ } else {
+ Notification.BubbleMetadata metadata = b.getEntry().getBubbleMetadata();
+ Icon ic = metadata.getIcon();
+ return ic.loadDrawable(context);
+ }
+ }
- BitmapInfo getBadgedBitmap(Bubble b) {
+ /**
+ * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
+ * will include the workprofile indicator on the badge if appropriate.
+ */
+ BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon) {
Bitmap userBadgedBitmap = createIconBitmap(
- b.getUserBadgedAppIcon(), 1f, getBadgeSize());
+ userBadgedAppIcon, 1f, getBadgeSize());
Canvas c = new Canvas();
ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
c.setBitmap(userBadgedBitmap);
shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
- BitmapInfo bitmapInfo = createIconBitmap(userBadgedBitmap);
- return bitmapInfo;
+ return createIconBitmap(userBadgedBitmap);
}
+ /**
+ * Returns a {@link BitmapInfo} for the entire bubble icon including the badge.
+ */
BitmapInfo getBubbleBitmap(Drawable bubble, BitmapInfo badge) {
BitmapInfo bubbleIconInfo = createBadgedIconBitmap(bubble,
null /* user */,
@@ -64,5 +88,4 @@ public class BubbleIconFactory extends BaseIconFactory {
new BitmapDrawable(mContext.getResources(), badge.icon));
return bubbleIconInfo;
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 245b2320f99d..898768311031 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -516,14 +516,7 @@ public class BubbleStackView extends FrameLayout {
* Handle theme changes.
*/
public void onThemeChanged() {
- // Recreate icon factory to update default adaptive icon scale.
- mBubbleIconFactory = new BubbleIconFactory(mContext);
setUpFlyout();
- for (Bubble b: mBubbleData.getBubbles()) {
- b.getIconView().setBubbleIconFactory(mBubbleIconFactory);
- b.getIconView().updateViews();
- b.getExpandedView().applyThemeAttrs();
- }
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -749,10 +742,6 @@ public class BubbleStackView extends FrameLayout {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
- bubble.setBubbleIconFactory(mBubbleIconFactory);
- bubble.inflate(mInflater, this);
- bubble.getIconView().updateViews();
-
// Set the dot position to the opposite of the side the stack is resting on, since the stack
// resting slightly off-screen would result in the dot also being off-screen.
bubble.getIconView().setDotPosition(
@@ -1567,9 +1556,6 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (mIsExpanded) {
- // First update the view so that it calculates a new height (ensuring the y position
- // calculation is correct)
- mExpandedBubble.getExpandedView().updateView();
final float y = getExpandedViewY();
if (!mExpandedViewYAnim.isRunning()) {
// We're not animating so set the value
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
new file mode 100644
index 000000000000..41f5028ed5c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -0,0 +1,182 @@
+/*
+ * 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.bubbles;
+
+import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
+import static com.android.systemui.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.PathParser;
+import android.view.LayoutInflater;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.systemui.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Simple task to inflate views & load necessary info to display a bubble.
+ */
+public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
+
+
+ /**
+ * Callback to find out when the bubble has been inflated & necessary data loaded.
+ */
+ public interface Callback {
+ /**
+ * Called when data has been loaded for the bubble.
+ */
+ void onBubbleViewsReady(Bubble bubble);
+ }
+
+ private Bubble mBubble;
+ private WeakReference<Context> mContext;
+ private WeakReference<BubbleStackView> mStackView;
+ private BubbleIconFactory mIconFactory;
+ private Callback mCallback;
+
+ /**
+ * Creates a task to load information for the provided {@link Bubble}. Once all info
+ * is loaded, {@link Callback} is notified.
+ */
+ BubbleViewInfoTask(Bubble b,
+ Context context,
+ BubbleStackView stackView,
+ BubbleIconFactory factory,
+ Callback c) {
+ mBubble = b;
+ mContext = new WeakReference<>(context);
+ mStackView = new WeakReference<>(stackView);
+ mIconFactory = factory;
+ mCallback = c;
+ }
+
+ @Override
+ protected BubbleViewInfo doInBackground(Void... voids) {
+ return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble);
+ }
+
+ @Override
+ protected void onPostExecute(BubbleViewInfo viewInfo) {
+ if (viewInfo != null) {
+ mBubble.setViewInfo(viewInfo);
+ if (mCallback != null && !isCancelled()) {
+ mCallback.onBubbleViewsReady(mBubble);
+ }
+ }
+ }
+
+ static class BubbleViewInfo {
+ BadgedImageView imageView;
+ BubbleExpandedView expandedView;
+ ShortcutInfo shortcutInfo;
+ String appName;
+ Bitmap badgedBubbleImage;
+ int dotColor;
+ Path dotPath;
+
+ @Nullable
+ static BubbleViewInfo populate(Context c, BubbleStackView stackView,
+ BubbleIconFactory iconFactory, Bubble b) {
+ BubbleViewInfo info = new BubbleViewInfo();
+
+ // View inflation: only should do this once per bubble
+ if (!b.isInflated()) {
+ LayoutInflater inflater = LayoutInflater.from(c);
+ info.imageView = (BadgedImageView) inflater.inflate(
+ R.layout.bubble_view, stackView, false /* attachToRoot */);
+
+ info.expandedView = (BubbleExpandedView) inflater.inflate(
+ R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
+ info.expandedView.setStackView(stackView);
+ }
+
+ StatusBarNotification sbn = b.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+
+ // Shortcut info for this bubble
+ String shortcutId = sbn.getNotification().getShortcutId();
+ if (BubbleExperimentConfig.useShortcutInfoToBubble(c)
+ && shortcutId != null) {
+ info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
+ packageName,
+ sbn.getUser(), shortcutId);
+ }
+
+ // App name & app icon
+ PackageManager pm = c.getPackageManager();
+ ApplicationInfo appInfo;
+ Drawable badgedIcon;
+ try {
+ appInfo = pm.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (appInfo != null) {
+ info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+ }
+ Drawable appIcon = pm.getApplicationIcon(packageName);
+ badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser());
+ } catch (PackageManager.NameNotFoundException exception) {
+ // If we can't find package... don't think we should show the bubble.
+ Log.w(TAG, "Unable to find package: " + packageName);
+ return null;
+ }
+
+ // Badged bubble image
+ Drawable bubbleDrawable = iconFactory.getBubbleDrawable(b, c);
+ BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
+ info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
+ badgeBitmapInfo).icon;
+
+ // Dot color & placement
+ Path iconPath = PathParser.createPathFromPathData(
+ c.getResources().getString(com.android.internal.R.string.config_icon_mask));
+ Matrix matrix = new Matrix();
+ float scale = iconFactory.getNormalizer().getScale(bubbleDrawable,
+ null /* outBounds */, null /* path */, null /* outMaskShape */);
+ float radius = DEFAULT_PATH_SIZE / 2f;
+ matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+ radius /* pivot y */);
+ iconPath.transform(matrix);
+ info.dotPath = iconPath;
+ info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+ Color.WHITE, WHITE_SCRIM_ALPHA);
+ return info;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index f032a33c45a9..534f350e4898 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -23,6 +23,7 @@ import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
+import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -131,6 +132,12 @@ public class SystemServicesModule {
@Singleton
@Provides
+ static NotificationManager provideNotificationManager(Context context) {
+ return context.getSystemService(NotificationManager.class);
+ }
+
+ @Singleton
+ @Provides
static PackageManagerWrapper providePackageManagerWrapper() {
return PackageManagerWrapper.getInstance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 05a234f1e431..d008e665d171 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -80,8 +80,7 @@ public class DozeSensors {
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy,
- DozeLog dozeLog) {
+ Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
mContext = context;
mAlarmManager = alarmManager;
mSensorManager = sensorManager;
@@ -154,7 +153,7 @@ public class DozeSensors {
};
mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
-
+ setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
if (proximityEvent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 2d6b946fc67e..722dc038f853 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -103,8 +103,7 @@ public class DozeTriggers implements DozeMachine.Part {
mWakeLock = wakeLock;
mAllowPulseTriggers = allowPulseTriggers;
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
- config, wakeLock, this::onSensor, this::onProximityFar,
- dozeParameters.getPolicy(), dozeLog);
+ config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index d79e38332af5..7bc2a0d5c97d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -92,7 +92,10 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
boolean nightMode = (mContext.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
- if (isAuto && !powerSave) {
+ if (powerSave) {
+ state.secondaryLabel = mContext.getResources().getString(
+ R.string.quick_settings_dark_mode_secondary_label_battery_saver);
+ } else if (isAuto) {
state.secondaryLabel = mContext.getResources().getString(nightMode
? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
: R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
@@ -100,9 +103,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
state.secondaryLabel = null;
}
state.value = nightMode;
- state.label = mContext.getString(powerSave
- ? R.string.quick_settings_ui_mode_night_label_battery_saver
- : R.string.quick_settings_ui_mode_night_label);
+ state.label = mContext.getString(R.string.quick_settings_ui_mode_night_label);
state.icon = mIcon;
state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
? state.label
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 0383dee4f9c3..d3ccbeb0afb1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -371,7 +371,8 @@ public class RecordingService extends Service {
Bitmap thumbnailBitmap = null;
try {
ContentResolver resolver = getContentResolver();
- Size size = Point.convert(MediaStore.ThumbnailConstants.MINI_SIZE);
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ Size size = new Size(metrics.widthPixels, metrics.heightPixels / 2);
thumbnailBitmap = resolver.loadThumbnail(uri, size, null);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail: " + e.getMessage());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 76925b43cfb8..2f401e5271d8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -23,6 +23,8 @@ import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
@@ -48,7 +50,9 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -275,6 +279,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
Context context = mParams.context;
+ ContentResolver resolver = context.getContentResolver();
Bitmap image = mParams.image;
Resources r = context.getResources();
@@ -284,23 +289,27 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
// Save the screenshot to the MediaStore
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
- params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
- + Environment.DIRECTORY_SCREENSHOTS);
-
- final Uri uri = MediaStore.createPending(context, params);
- final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+ + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+ values.put(MediaColumns.DISPLAY_NAME, mImageFileName);
+ values.put(MediaColumns.MIME_TYPE, "image/png");
+ values.put(MediaColumns.DATE_ADDED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
+
+ final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try {
// First, write the actual data for our screenshot
- try (OutputStream out = session.openOutputStream()) {
+ try (OutputStream out = resolver.openOutputStream(uri)) {
if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
throw new IOException("Failed to compress");
}
}
// Next, write metadata to help index the screenshot
- try (ParcelFileDescriptor pfd = session.open()) {
+ try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
exif.setAttribute(ExifInterface.TAG_SOFTWARE,
@@ -327,12 +336,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
exif.saveAttributes();
}
- session.publish();
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ resolver.update(uri, values, null, null);
} catch (Exception e) {
- session.abandon();
+ resolver.delete(uri, null);
throw e;
- } finally {
- IoUtils.closeQuietly(session);
}
populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 9f79785be56e..077d2602e43f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -21,6 +21,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -97,7 +98,7 @@ public abstract class CurrentUserTracker {
if (!mReceiverRegistered) {
mCurrentUserId = ActivityManager.getCurrentUser();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter);
+ mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
mReceiverRegistered = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 9a64b30ed918..97dd3daae341 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -19,9 +19,7 @@ package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
-import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -33,8 +31,6 @@ import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.systemui.dagger.qualifiers.MainHandler;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import java.util.ArrayList;
@@ -52,31 +48,33 @@ import javax.inject.Singleton;
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
- // Dependencies:
- private final NotificationEntryManager mEntryManager;
- private final NotificationGroupManager mGroupManager;
-
private final Context mContext;
+ private final NotificationManager mNotificationManager;
private final Handler mMainHandler;
+ private final List<NotifServiceListener> mNotificationListeners = new ArrayList<>();
private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
- @Nullable private NotifServiceListener mDownstreamListener;
@Inject
- public NotificationListener(Context context, @MainHandler Handler mainHandler,
- NotificationEntryManager notificationEntryManager,
- NotificationGroupManager notificationGroupManager) {
+ public NotificationListener(
+ Context context,
+ NotificationManager notificationManager,
+ @MainHandler Handler mainHandler) {
mContext = context;
+ mNotificationManager = notificationManager;
mMainHandler = mainHandler;
- mEntryManager = notificationEntryManager;
- mGroupManager = notificationGroupManager;
}
- public void addNotificationSettingsListener(NotificationSettingsListener listener) {
- mSettingsListeners.add(listener);
+ /** Registers a listener that's notified when notifications are added/removed/etc. */
+ public void addNotificationListener(NotifServiceListener listener) {
+ if (mNotificationListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener is already added");
+ }
+ mNotificationListeners.add(listener);
}
- public void setDownstreamListener(NotifServiceListener downstreamListener) {
- mDownstreamListener = downstreamListener;
+ /** Registers a listener that's notified when any notification-related settings change. */
+ public void addNotificationSettingsListener(NotificationSettingsListener listener) {
+ mSettingsListeners.add(listener);
}
@Override
@@ -102,14 +100,13 @@ public class NotificationListener extends NotificationListenerWithPlugins {
final RankingMap completeMap = new RankingMap(newRankings.toArray(new Ranking[0]));
for (StatusBarNotification sbn : notifications) {
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationPosted(sbn, completeMap);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationPosted(sbn, completeMap);
}
- mEntryManager.addNotification(sbn, completeMap);
}
});
- NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
- onSilentStatusBarIconsVisibilityChanged(noMan.shouldHideSilentStatusBarIcons());
+ onSilentStatusBarIconsVisibilityChanged(
+ mNotificationManager.shouldHideSilentStatusBarIcons());
}
@Override
@@ -120,34 +117,8 @@ public class NotificationListener extends NotificationListenerWithPlugins {
mMainHandler.post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationPosted(sbn, rankingMap);
- }
-
- String key = sbn.getKey();
- boolean isUpdate = mEntryManager.getActiveNotificationUnfiltered(key) != null;
- // In case we don't allow child notifications, we ignore children of
- // notifications that have a summary, since` we're not going to show them
- // anyway. This is true also when the summary is canceled,
- // because children are automatically canceled by NoMan in that case.
- if (!ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
-
- // Remove existing notification to avoid stale data.
- if (isUpdate) {
- mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
- } else {
- mEntryManager.updateRanking(rankingMap, "onNotificationPosted");
- }
- return;
- }
- if (isUpdate) {
- mEntryManager.updateNotification(sbn, rankingMap);
- } else {
- mEntryManager.addNotification(sbn, rankingMap);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationPosted(sbn, rankingMap);
}
});
}
@@ -158,12 +129,10 @@ public class NotificationListener extends NotificationListenerWithPlugins {
int reason) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
- final String key = sbn.getKey();
mMainHandler.post(() -> {
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationRemoved(sbn, rankingMap, reason);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationRemoved(sbn, rankingMap, reason);
}
- mEntryManager.removeNotification(key, rankingMap, reason);
});
}
}
@@ -179,10 +148,9 @@ public class NotificationListener extends NotificationListenerWithPlugins {
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
mMainHandler.post(() -> {
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationRankingUpdate(rankingMap);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationRankingUpdate(r);
}
- mEntryManager.updateNotificationRanking(r);
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index dbefc7b4514e..43b9fbc909dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -33,6 +33,8 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
@@ -174,6 +176,11 @@ public class NotificationEntryManager implements
mKeyguardEnvironment = keyguardEnvironment;
}
+ /** Once called, the NEM will start processing notification events from system server. */
+ public void attach(NotificationListener notificationListener) {
+ notificationListener.addNotificationListener(mNotifListener);
+ }
+
/** Adds a {@link NotificationEntryListener}. */
public void addNotificationEntryListener(NotificationEntryListener listener) {
mNotificationEntryListeners.add(listener);
@@ -318,6 +325,36 @@ public class NotificationEntryManager implements
}
}
+ private final NotifServiceListener mNotifListener = new NotifServiceListener() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ final boolean isUpdate = mActiveNotifications.containsKey(sbn.getKey());
+ if (isUpdate) {
+ updateNotification(sbn, rankingMap);
+ } else {
+ addNotification(sbn, rankingMap);
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
+ }
+
+ @Override
+ public void onNotificationRemoved(
+ StatusBarNotification sbn,
+ RankingMap rankingMap,
+ int reason) {
+ removeNotification(sbn.getKey(), rankingMap, reason);
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ updateNotificationRanking(rankingMap);
+ }
+ };
+
/**
* Equivalent to the old NotificationData#add
* @param entry - an entry which is prepared for display
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 6c61923332ea..3afd6235b287 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -32,7 +32,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -120,11 +119,6 @@ public class NotificationFilter {
return true;
}
- if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- return true;
- }
-
if (getFsc().isDisclosureNotification(sbn)
&& !getFsc().isDisclosureNeededForUser(sbn.getUserId())) {
// this is a foreground-service disclosure for a user that does not need to show one
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 052473ae21d9..601b3e053e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.collection;
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
@@ -68,8 +67,7 @@ public abstract class ListEntry {
return mParent;
}
- @VisibleForTesting
- public void setParent(@Nullable GroupEntry parent) {
+ void setParent(@Nullable GroupEntry parent) {
if (!Objects.equals(mParent, parent)) {
invalidateParent();
mParent = parent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 6f085c0ce7c0..7f85c88865f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -116,7 +116,7 @@ public class NotifCollection {
}
mAttached = true;
- listenerService.setDownstreamListener(mNotifServiceListener);
+ listenerService.addNotificationListener(mNotifServiceListener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
index a1cfb5424c08..f0a003f2ff5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
@@ -20,9 +20,12 @@ import static com.android.systemui.statusbar.notification.collection.GroupEntry.
import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_PENDING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FILTERING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_RENDER_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
@@ -63,16 +66,17 @@ public class NotifListBuilderImpl implements NotifListBuilder {
private final SystemClock mSystemClock;
private final NotifLog mNotifLog;
- private final List<ListEntry> mNotifList = new ArrayList<>();
+ private List<ListEntry> mNotifList = new ArrayList<>();
+ private List<ListEntry> mNewNotifList = new ArrayList<>();
private final PipelineState mPipelineState = new PipelineState();
private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
- private final List<ListEntry> mNewEntries = new ArrayList<>();
private int mIterationCount = 0;
- private final List<NotifFilter> mNotifFilters = new ArrayList<>();
+ private final List<NotifFilter> mNotifPreGroupFilters = new ArrayList<>();
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
+ private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
@@ -138,12 +142,21 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
@Override
- public void addFilter(NotifFilter filter) {
+ public void addPreGroupFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mNotifFilters.add(filter);
- filter.setInvalidationListener(this::onFilterInvalidated);
+ mNotifPreGroupFilters.add(filter);
+ filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
+ }
+
+ @Override
+ public void addPreRenderFilter(NotifFilter filter) {
+ Assert.isMainThread();
+ mPipelineState.requireState(STATE_IDLE);
+
+ mNotifPreRenderFilters.add(filter);
+ filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
}
@Override
@@ -202,15 +215,15 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
};
- private void onFilterInvalidated(NotifFilter filter) {
+ private void onPreGroupFilterInvalidated(NotifFilter filter) {
Assert.isMainThread();
- mNotifLog.log(NotifEvent.FILTER_INVALIDATED, String.format(
+ mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format(
"Filter \"%s\" invalidated; pipeline state is %d",
filter.getName(),
mPipelineState.getState()));
- rebuildListIfBefore(STATE_FILTERING);
+ rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
}
private void onPromoterInvalidated(NotifPromoter filter) {
@@ -235,6 +248,17 @@ public class NotifListBuilderImpl implements NotifListBuilder {
rebuildListIfBefore(STATE_SORTING);
}
+ private void onPreRenderFilterInvalidated(NotifFilter filter) {
+ Assert.isMainThread();
+
+ mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format(
+ "Filter \"%s\" invalidated; pipeline state is %d",
+ filter.getName(),
+ mPipelineState.getState()));
+
+ rebuildListIfBefore(STATE_PRE_RENDER_FILTERING);
+ }
+
private void onNotifComparatorInvalidated(NotifComparator comparator) {
Assert.isMainThread();
@@ -247,6 +271,17 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
/**
+ * Points mNotifList to the list stored in mNewNotifList.
+ * Reuses the (emptied) mNotifList as mNewNotifList.
+ */
+ private void applyNewNotifList() {
+ mNotifList.clear();
+ List<ListEntry> emptyList = mNotifList;
+ mNotifList = mNewNotifList;
+ mNewNotifList = emptyList;
+ }
+
+ /**
* The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
* details on our contracts with other code.
*
@@ -261,35 +296,47 @@ public class NotifListBuilderImpl implements NotifListBuilder {
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
mPipelineState.setState(STATE_BUILD_STARTED);
- // Step 1: Filtering and initial grouping
- // Filter out any notifs that shouldn't be shown right now and cluster any that are part of
- // a group
- mPipelineState.incrementTo(STATE_FILTERING);
- mNotifList.clear();
- mNewEntries.clear();
- filterAndGroup(mAllEntries, mNotifList, mNewEntries);
- pruneIncompleteGroups(mNotifList, mNewEntries);
+ // Step 1: Reset notification states
+ mPipelineState.incrementTo(STATE_RESETTING);
+ resetNotifs();
+
+ // Step 2: Filter out any notifications that shouldn't be shown right now
+ mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
+ filterNotifs(mAllEntries, mNotifList, mNotifPreGroupFilters);
- // Step 2: Group transforming
+ // Step 3: Group notifications with the same group key and set summaries
+ mPipelineState.incrementTo(STATE_GROUPING);
+ groupNotifs(mNotifList, mNewNotifList);
+ applyNewNotifList();
+ pruneIncompleteGroups(mNotifList);
+
+ // Step 4: Group transforming
// Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
- dispatchOnBeforeTransformGroups(mReadOnlyNotifList, mNewEntries);
+ dispatchOnBeforeTransformGroups(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_TRANSFORMING);
promoteNotifs(mNotifList);
- pruneIncompleteGroups(mNotifList, mNewEntries);
+ pruneIncompleteGroups(mNotifList);
- // Step 3: Sort
+ // Step 5: Sort
// Assign each top-level entry a section, then sort the list by section and then within
// section by our list of custom comparators
dispatchOnBeforeSort(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_SORTING);
sortList();
- // Step 4: Lock in our group structure and log anything that's changed since the last run
+ // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
+ // Now filters can see grouping information to determine whether to filter or not
+ mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
+ filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
+ applyNewNotifList();
+ pruneIncompleteGroups(mNotifList);
+
+ // Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
logParentingChanges();
freeEmptyGroups();
- // Step 5: Dispatch the new list, first to any listeners and then to the view layer
+ // Step 6: Dispatch the new list, first to any listeners and then to the view layer
mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
+ dumpList(mNotifList));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
@@ -297,20 +344,14 @@ public class NotifListBuilderImpl implements NotifListBuilder {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
}
- // Step 6: We're done!
+ // Step 7: We're done!
mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
"Notif list build #" + mIterationCount + " completed");
mPipelineState.setState(STATE_IDLE);
mIterationCount++;
}
- private void filterAndGroup(
- Collection<NotificationEntry> entries,
- List<ListEntry> out,
- List<ListEntry> newlyVisibleEntries) {
-
- long now = mSystemClock.uptimeMillis();
-
+ private void resetNotifs() {
for (GroupEntry group : mGroups.values()) {
group.setPreviousParent(group.getParent());
group.setParent(null);
@@ -318,22 +359,57 @@ public class NotifListBuilderImpl implements NotifListBuilder {
group.setSummary(null);
}
- for (NotificationEntry entry : entries) {
+ for (NotificationEntry entry : mAllEntries) {
entry.setPreviousParent(entry.getParent());
entry.setParent(null);
- // See if we should filter out this notification
- boolean shouldFilterOut = applyFilters(entry, now);
- if (shouldFilterOut) {
- continue;
- }
-
if (entry.mFirstAddedIteration == -1) {
entry.mFirstAddedIteration = mIterationCount;
- newlyVisibleEntries.add(entry);
}
+ }
+
+ mNotifList.clear();
+ }
+
+ private void filterNotifs(Collection<? extends ListEntry> entries,
+ List<ListEntry> out, List<NotifFilter> filters) {
+ final long now = mSystemClock.uptimeMillis();
+ for (ListEntry entry : entries) {
+ if (entry instanceof GroupEntry) {
+ final GroupEntry groupEntry = (GroupEntry) entry;
+
+ // apply filter on its summary
+ final NotificationEntry summary = groupEntry.getRepresentativeEntry();
+ if (applyFilters(summary, now, filters)) {
+ groupEntry.setSummary(null);
+ annulAddition(summary);
+ }
+
+ // apply filter on its children
+ final List<NotificationEntry> children = groupEntry.getRawChildren();
+ for (int j = children.size() - 1; j >= 0; j--) {
+ final NotificationEntry child = children.get(j);
+ if (applyFilters(child, now, filters)) {
+ children.remove(child);
+ annulAddition(child);
+ }
+ }
- // Otherwise, group it
+ out.add(groupEntry);
+ } else {
+ if (applyFilters((NotificationEntry) entry, now, filters)) {
+ annulAddition(entry);
+ } else {
+ out.add(entry);
+ }
+ }
+ }
+ }
+
+ private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+ for (ListEntry listEntry : entries) {
+ // since grouping hasn't happened yet, all notifs are NotificationEntries
+ NotificationEntry entry = (NotificationEntry) listEntry;
if (entry.getSbn().isGroup()) {
final String topLevelKey = entry.getSbn().getGroupKey();
@@ -341,7 +417,6 @@ public class NotifListBuilderImpl implements NotifListBuilder {
if (group == null) {
group = new GroupEntry(topLevelKey);
group.mFirstAddedIteration = mIterationCount;
- newlyVisibleEntries.add(group);
mGroups.put(topLevelKey, group);
}
if (group.getParent() == null) {
@@ -367,9 +442,9 @@ public class NotifListBuilderImpl implements NotifListBuilder {
if (entry.getSbn().getPostTime()
> existingSummary.getSbn().getPostTime()) {
group.setSummary(entry);
- annulAddition(existingSummary, out, newlyVisibleEntries);
+ annulAddition(existingSummary, out);
} else {
- annulAddition(entry, out, newlyVisibleEntries);
+ annulAddition(entry, out);
}
}
} else {
@@ -411,10 +486,7 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
}
- private void pruneIncompleteGroups(
- List<ListEntry> shadeList,
- List<ListEntry> newlyVisibleEntries) {
-
+ private void pruneIncompleteGroups(List<ListEntry> shadeList) {
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
@@ -431,7 +503,7 @@ public class NotifListBuilderImpl implements NotifListBuilder {
shadeList.add(summary);
group.setSummary(null);
- annulAddition(group, shadeList, newlyVisibleEntries);
+ annulAddition(group, shadeList);
} else if (group.getSummary() == null
|| children.size() < MIN_CHILDREN_FOR_GROUP) {
@@ -444,7 +516,7 @@ public class NotifListBuilderImpl implements NotifListBuilder {
if (group.getSummary() != null) {
final NotificationEntry summary = group.getSummary();
group.setSummary(null);
- annulAddition(summary, shadeList, newlyVisibleEntries);
+ annulAddition(summary, shadeList);
}
for (int j = 0; j < children.size(); j++) {
@@ -454,7 +526,7 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
children.clear();
- annulAddition(group, shadeList, newlyVisibleEntries);
+ annulAddition(group, shadeList);
}
}
}
@@ -468,10 +540,7 @@ public class NotifListBuilderImpl implements NotifListBuilder {
* Before calling this method, the entry must already have been removed from its parent. If
* it's a group, its summary must be null and its children must be empty.
*/
- private void annulAddition(
- ListEntry entry,
- List<ListEntry> shadeList,
- List<ListEntry> newlyVisibleEntries) {
+ private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
// This function does very little, but if any of its assumptions are violated (and it has a
// lot of them), it will put the system into an inconsistent state. So we check all of them
@@ -508,13 +577,18 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
}
+ annulAddition(entry);
+
+ }
+
+ /**
+ * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+ * This can happen if the entry is removed from a group that was broken up or if the entry was
+ * filtered out during any of the filtering steps.
+ */
+ private void annulAddition(ListEntry entry) {
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
- if (!newlyVisibleEntries.remove(entry)) {
- throw new IllegalStateException("Cannot late-filter entry " + entry.getKey() + " "
- + entry + " from " + newlyVisibleEntries + " "
- + entry.mFirstAddedIteration);
- }
entry.mFirstAddedIteration = -1;
}
}
@@ -606,8 +680,8 @@ public class NotifListBuilderImpl implements NotifListBuilder {
return cmp;
};
- private boolean applyFilters(NotificationEntry entry, long now) {
- NotifFilter filter = findRejectingFilter(entry, now);
+ private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
+ NotifFilter filter = findRejectingFilter(entry, now, filters);
if (filter != entry.mExcludingFilter) {
if (entry.mExcludingFilter == null) {
@@ -637,9 +711,12 @@ public class NotifListBuilderImpl implements NotifListBuilder {
return filter != null;
}
- @Nullable private NotifFilter findRejectingFilter(NotificationEntry entry, long now) {
- for (int i = 0; i < mNotifFilters.size(); i++) {
- NotifFilter filter = mNotifFilters.get(i);
+ @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
+ List<NotifFilter> filters) {
+ final int size = filters.size();
+
+ for (int i = 0; i < size; i++) {
+ NotifFilter filter = filters.get(i);
if (filter.shouldFilterOut(entry, now)) {
return filter;
}
@@ -691,12 +768,9 @@ public class NotifListBuilderImpl implements NotifListBuilder {
}
}
- private void dispatchOnBeforeTransformGroups(
- List<ListEntry> entries,
- List<ListEntry> newlyVisibleEntries) {
+ private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
- mOnBeforeTransformGroupsListeners.get(i)
- .onBeforeTransformGroups(entries, newlyVisibleEntries);
+ mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index b68cb0dd3320..5e7dd98fa9da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -55,7 +55,7 @@ public class DeviceProvisionedCoordinator implements Coordinator {
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 378599b79cc0..ee841c2b4e14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -83,7 +83,7 @@ public class ForegroundCoordinator implements Coordinator {
mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
// filter out foreground service notifications that aren't necessary anymore
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 232246e4f704..9312c2260d46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -85,7 +85,7 @@ public class KeyguardCoordinator implements Coordinator {
@Override
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
setupInvalidateNotifListCallbacks();
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreRenderFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -131,9 +131,8 @@ public class KeyguardCoordinator implements Coordinator {
}
}
- // ... neither this notification nor its summary have high enough priority
+ // ... neither this notification nor its group have high enough priority
// to be shown on the lockscreen
- // TODO: grouping hasn't happened yet (b/145134683)
if (entry.getParent() != null) {
final GroupEntry parent = entry.getParent();
if (priorityExceedsLockscreenShowingThreshold(parent)) {
@@ -152,11 +151,10 @@ public class KeyguardCoordinator implements Coordinator {
}
if (NotificationUtils.useNewInterruptionModel(mContext)
&& hideSilentNotificationsOnLockscreen()) {
- // TODO: make sure in the NewNotifPipeline that entry.isHighPriority() has been
- // correctly updated before reaching this point (b/145134683)
return entry.isHighPriority();
} else {
- return !entry.getRepresentativeEntry().getRanking().isAmbient();
+ return entry.getRepresentativeEntry() != null
+ && !entry.getRepresentativeEntry().getRanking().isAmbient();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 24e7a79e9588..0751aa814215 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -46,7 +46,7 @@ public class RankingCoordinator implements Coordinator {
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
index 15d3b9222cd2..758092417ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
@@ -59,11 +59,12 @@ import java.util.List;
public interface NotifListBuilder {
/**
- * Registers a filter with the pipeline. Filters are called on each notification in the order
- * that they were registered. If any filter returns true, the notification is removed from the
- * pipeline (and no other filters are called on that notif).
+ * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
+ * are called on each notification in the order that they were registered. If any filter
+ * returns true, the notification is removed from the pipeline (and no other filters are
+ * called on that notif).
*/
- void addFilter(NotifFilter filter);
+ void addPreGroupFilter(NotifFilter filter);
/**
* Registers a promoter with the pipeline. Promoters are able to promote child notifications to
@@ -91,6 +92,15 @@ public interface NotifListBuilder {
void setComparators(List<NotifComparator> comparators);
/**
+ * Registers a filter with the pipeline to filter right before rendering the list (after
+ * pre-group filtering, grouping, promoting and sorting occurs). Filters are
+ * called on each notification in the order that they were registered. If any filter returns
+ * true, the notification is removed from the pipeline (and no other filters are called on that
+ * notif).
+ */
+ void addPreRenderFilter(NotifFilter filter);
+
+ /**
* Called after notifications have been filtered and after the initial grouping has been
* performed but before NotifPromoters have had a chance to promote children out of groups.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index 170ff48ad7f1..d7a081510655 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -33,8 +33,6 @@ public interface OnBeforeTransformGroupsListener {
* @param list The current filtered and grouped list of (top-level) entries. Note that this is
* a live view into the current notif list and will change as the list moves through
* the pipeline.
- * @param newlyVisibleEntries The list of all entries (both top-level and children) who have
- * been added to the list for the first time.
*/
- void onBeforeTransformGroups(List<ListEntry> list, List<ListEntry> newlyVisibleEntries);
+ void onBeforeTransformGroups(List<ListEntry> list);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index ad4bbd915787..85f828d9e3c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -78,18 +78,24 @@ public class PipelineState {
public static final int STATE_IDLE = 0;
public static final int STATE_BUILD_PENDING = 1;
public static final int STATE_BUILD_STARTED = 2;
- public static final int STATE_FILTERING = 3;
- public static final int STATE_TRANSFORMING = 4;
- public static final int STATE_SORTING = 5;
- public static final int STATE_FINALIZING = 6;
+ public static final int STATE_RESETTING = 3;
+ public static final int STATE_PRE_GROUP_FILTERING = 4;
+ public static final int STATE_GROUPING = 5;
+ public static final int STATE_TRANSFORMING = 6;
+ public static final int STATE_SORTING = 7;
+ public static final int STATE_PRE_RENDER_FILTERING = 8;
+ public static final int STATE_FINALIZING = 9;
@IntDef(prefix = { "STATE_" }, value = {
STATE_IDLE,
STATE_BUILD_PENDING,
STATE_BUILD_STARTED,
- STATE_FILTERING,
+ STATE_RESETTING,
+ STATE_PRE_GROUP_FILTERING,
+ STATE_GROUPING,
STATE_TRANSFORMING,
STATE_SORTING,
+ STATE_PRE_RENDER_FILTERING,
STATE_FINALIZING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
index 685eac88de34..e6189edc2f3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
@@ -20,8 +20,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
- * Pluggable for participating in notif filtering. See
- * {@link NotifListBuilder#addFilter(NotifFilter)}.
+ * Pluggable for participating in notif filtering.
+ * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
*/
public abstract class NotifFilter extends Pluggable<NotifFilter> {
protected NotifFilter(String name) {
@@ -34,7 +34,11 @@ public abstract class NotifFilter extends Pluggable<NotifFilter> {
* This doesn't necessarily mean that your filter will get called on every notification,
* however. If another filter returns true before yours, we'll skip straight to the next notif.
*
- * @param entry The entry in question
+ * @param entry The entry in question.
+ * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+ * this entry will not have any grouping nor sorting information.
+ * If this filter is registered via {@link NotifListBuilder#addPreRenderFilter},
+ * this entry will have grouping and sorting information.
* @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
* pipeline execution. This value will be the same for all pluggable calls made
* during this pipeline run, giving pluggables a stable concept of "now" to compare
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 3b06220c7c9c..c18af800e183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -88,13 +88,14 @@ public class NotifEvent extends RichEvent {
START_BUILD_LIST,
DISPATCH_FINAL_LIST,
LIST_BUILD_COMPLETE,
- FILTER_INVALIDATED,
+ PRE_GROUP_FILTER_INVALIDATED,
PROMOTER_INVALIDATED,
SECTIONS_PROVIDER_INVALIDATED,
COMPARATOR_INVALIDATED,
PARENT_CHANGED,
FILTER_CHANGED,
PROMOTER_CHANGED,
+ PRE_RENDER_FILTER_INVALIDATED,
// NotificationEntryManager events:
NOTIF_ADDED,
@@ -127,6 +128,7 @@ public class NotifEvent extends RichEvent {
"ParentChanged",
"FilterChanged",
"PromoterChanged",
+ "FinalFilterInvalidated",
// NEM event labels:
"NotifAdded",
@@ -152,14 +154,15 @@ public class NotifEvent extends RichEvent {
public static final int START_BUILD_LIST = 2;
public static final int DISPATCH_FINAL_LIST = 3;
public static final int LIST_BUILD_COMPLETE = 4;
- public static final int FILTER_INVALIDATED = 5;
+ public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
- private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 12;
+ public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
+ private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
/**
* Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 987b52dbc2e8..784673e64ff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -249,7 +249,7 @@ private fun addBadgeToDrawable(
}
}
-private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
+fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
entry.row
?.childrenWithId(R.id.expanded)
?.mapNotNull { it as? ViewGroup }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9ac5b44f8639..65423e970cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -813,15 +813,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* @param parent the new parent notification
*/
public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
- boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
mNotificationParent.setChildIsExpanding(false);
mNotificationParent.setExtraWidthForClipping(0.0f);
mNotificationParent.setMinimumHeightForClipping(0);
}
- mNotificationParent = childInGroup ? parent : null;
- mPrivateLayout.setIsChildInGroup(childInGroup);
- mNotificationInflater.setIsChildInGroup(childInGroup);
+ mNotificationParent = isChildInGroup ? parent : null;
+ mPrivateLayout.setIsChildInGroup(isChildInGroup);
+ mNotificationInflater.setIsChildInGroup(isChildInGroup);
resetBackgroundAlpha();
updateBackgroundForGroupState();
updateClickAndFocus();
@@ -2354,8 +2353,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void onChildrenCountChanged() {
- mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
- && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
+ mIsSummaryWithChildren = mChildrenContainer != null
+ && mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 12819535cfb6..8c3420a522ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -35,8 +35,6 @@ import javax.inject.Singleton;
@Singleton
public class LockscreenGestureLogger {
private ArrayMap<Integer, Integer> mLegacyMap;
- private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
- .setType(MetricsEvent.TYPE_ACTION);
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
@Inject
@@ -48,7 +46,7 @@ public class LockscreenGestureLogger {
}
public void write(int gesture, int length, int velocity) {
- mMetricsLogger.write(mLogMaker.setCategory(gesture)
+ mMetricsLogger.write(new LogMaker(gesture)
.setType(MetricsEvent.TYPE_ACTION)
.addTaggedData(MetricsEvent.FIELD_GESTURE_LENGTH, length)
.addTaggedData(MetricsEvent.FIELD_GESTURE_VELOCITY, velocity));
@@ -64,7 +62,7 @@ public class LockscreenGestureLogger {
*/
public void writeAtFractionalPosition(
int category, int xPercent, int yPercent, int rotation) {
- mMetricsLogger.write(mLogMaker.setCategory(category)
+ mMetricsLogger.write(new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.addTaggedData(MetricsEvent.FIELD_GESTURE_X_PERCENT, xPercent)
.addTaggedData(MetricsEvent.FIELD_GESTURE_Y_PERCENT, yPercent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index f3e9b6b3d155..183adeb037d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -37,12 +37,19 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.IndentingPrintWriter;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.statusbar.NotificationMediaManager;
import libcore.io.IoUtils;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
import javax.inject.Inject;
@@ -52,16 +59,15 @@ import javax.inject.Singleton;
* Manages the lockscreen wallpaper.
*/
@Singleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable {
+public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
+ Dumpable {
private static final String TAG = "LockscreenWallpaper";
- private final NotificationMediaManager mMediaManager =
- Dependency.get(NotificationMediaManager.class);
-
+ private final NotificationMediaManager mMediaManager;
private final WallpaperManager mWallpaperManager;
- private Handler mH;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final Handler mH;
private boolean mCached;
private Bitmap mCache;
@@ -74,10 +80,16 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
@Inject
public LockscreenWallpaper(WallpaperManager wallpaperManager,
@Nullable IWallpaperManager iWallpaperManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DumpController dumpController,
+ NotificationMediaManager mediaManager,
+ @MainHandler Handler mainHandler) {
+ dumpController.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
mCurrentUserId = ActivityManager.getCurrentUser();
mUpdateMonitor = keyguardUpdateMonitor;
+ mMediaManager = mediaManager;
+ mH = mainHandler;
if (iWallpaperManager != null) {
// Service is disabled on some devices like Automotive
@@ -89,14 +101,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
}
}
- void setHandler(Handler handler) {
- if (mH != null) {
- Log.wtfStack(TAG, "Handler has already been set. Trying to double initialize?");
- return;
- }
- mH = handler;
- }
-
public Bitmap getBitmap() {
if (mCached) {
return mCache;
@@ -227,6 +231,16 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println(getClass().getSimpleName() + ":");
+ IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ").increaseIndent();
+ iPw.println("mCached=" + mCached);
+ iPw.println("mCache=" + mCache);
+ iPw.println("mCurrentUserId=" + mCurrentUserId);
+ iPw.println("mSelectedUser=" + mSelectedUser);
+ }
+
private static class LoaderResult {
public final boolean success;
public final Bitmap bitmap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index b8aea9b2cd59..4c5bbce05261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -561,7 +561,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
protected void scheduleUpdate() {
- if (mUpdatePending) return;
+ if (mUpdatePending || mScrimBehind == null) return;
// Make sure that a frame gets scheduled.
mScrimBehind.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a9d760185fd6..f4c7e23f9cda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -246,9 +246,6 @@ public class StatusBar extends SystemUI implements DemoMode,
StatusBarStateController.StateListener, ActivityLaunchAnimator.Callback {
public static final boolean MULTIUSER_DEBUG = false;
- public static final boolean ENABLE_CHILD_NOTIFICATIONS
- = SystemProperties.getBoolean("debug.child_notifs", true);
-
protected static final int MSG_HIDE_RECENT_APPS = 1020;
protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
@@ -813,6 +810,8 @@ public class StatusBar extends SystemUI implements DemoMode,
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mWallpaperSupported =
+ mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
// Connect in to the status bar manager service
mCommandQueue.addCallback(this);
@@ -826,9 +825,6 @@ public class StatusBar extends SystemUI implements DemoMode,
createAndAddWindows(result);
- mWallpaperSupported =
- mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
-
if (mWallpaperSupported) {
// Make sure we always have the most current wallpaper info.
IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
@@ -1061,7 +1057,6 @@ public class StatusBar extends SystemUI implements DemoMode,
if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
- mLockscreenWallpaper.setHandler(mHandler);
}
mKeyguardIndicationController =
@@ -1271,6 +1266,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mFeatureFlags.isNewNotifPipelineEnabled()) {
mNewNotifPipeline.get().initialize(mNotificationListener);
}
+ mEntryManager.attach(mNotificationListener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
new file mode 100644
index 000000000000..62ae7b9d620f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -0,0 +1,717 @@
+/*
+ * 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.util.animation
+
+import android.os.Looper
+import android.util.ArrayMap
+import android.util.Log
+import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FlingAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
+import java.util.WeakHashMap
+
+/**
+ * Extension function for all objects which will return a PhysicsAnimator instance for that object.
+ */
+val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
+
+private const val TAG = "PhysicsAnimator"
+
+typealias EndAction = () -> Unit
+
+/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
+typealias UpdateMap<T> =
+ ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+/**
+ * Map of the animators associated with a given object. This ensures that only one animator
+ * per object exists.
+ */
+internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
+
+/**
+ * Default spring configuration to use for animations where stiffness and/or damping ratio
+ * were not provided.
+ */
+private val defaultSpring = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM,
+ SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+
+/** Default fling configuration to use for animations where friction was not provided. */
+private val defaultFling = PhysicsAnimator.FlingConfig(
+ friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
+
+/** Whether to log helpful debug information about animations. */
+private var verboseLogging = false
+
+/**
+ * Animator that uses physics-based animations to animate properties on views and objects. Physics
+ * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
+ * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
+ * also uses the builder pattern to configure and start animations.
+ *
+ * The physics animations are backed by [DynamicAnimation].
+ *
+ * @param T The type of the object being animated.
+ */
+class PhysicsAnimator<T> private constructor (val target: T) {
+
+ /** Data class for representing animation frame updates. */
+ data class AnimationUpdate(val value: Float, val velocity: Float)
+
+ /** [DynamicAnimation] instances for the given properties. */
+ private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
+ private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
+
+ /**
+ * Spring and fling configurations for the properties to be animated on the target. We'll
+ * configure and start the DynamicAnimations for these properties according to the provided
+ * configurations.
+ */
+ private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
+ private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
+
+ /**
+ * Animation listeners for the animation. These will be notified when each property animation
+ * updates or ends.
+ */
+ private val updateListeners = ArrayList<UpdateListener<T>>()
+ private val endListeners = ArrayList<EndListener<T>>()
+
+ /** End actions to run when all animations have completed. */
+ private val endActions = ArrayList<EndAction>()
+
+ /**
+ * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
+ * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
+ * just one permanent update and end listener to the DynamicAnimations.
+ */
+ internal var internalListeners = ArrayList<InternalListener>()
+
+ /**
+ * Action to run when [start] is called. This can be changed by
+ * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
+ * helpful test utilities.
+ */
+ internal var startAction: () -> Unit = ::startInternal
+
+ /**
+ * Springs a property to the given value, using the provided configuration settings.
+ *
+ * Springs are used when you know the exact value to which you want to animate. They can be
+ * configured with a start velocity (typically used when the spring is initiated by a touch
+ * event), but this velocity will be realistically attenuated as forces are applied to move the
+ * property towards the end value.
+ *
+ * If you find yourself repeating the same stiffness and damping ratios many times, consider
+ * storing a single [SpringConfig] instance and passing that in instead of individual values.
+ *
+ * @param property The property to spring to the given value. The property must be an instance
+ * of FloatPropertyCompat&lt;? super T&gt;. For example, if this is a
+ * PhysicsAnimator&lt;FrameLayout&gt;, you can use a FloatPropertyCompat&lt;FrameLayout&gt;, as
+ * well as a FloatPropertyCompat&lt;ViewGroup&gt;, and so on.
+ * @param toPosition The value to spring the given property to.
+ * @param startVelocity The initial velocity to use for the animation.
+ * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
+ * faster animations, while lower stiffness means a slower animation. Reasonable values for
+ * low, medium, and high stiffness can be found as constants in [SpringForce].
+ * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
+ * result in a less 'springy' animation, while lower values allow the animation to bounce
+ * back and forth for a longer time after reaching the final position. Reasonable values for
+ * low, medium, and high damping can be found in [SpringForce].
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ startVelocity: Float = 0f,
+ stiffness: Float = defaultSpring.stiffness,
+ dampingRatio: Float = defaultSpring.dampingRatio
+ ): PhysicsAnimator<T> {
+ if (verboseLogging) {
+ Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
+ }
+
+ springConfigs[property] =
+ SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
+ return this
+ }
+
+ /**
+ * Springs a property to a given value using the provided start velocity and configuration
+ * options.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ startVelocity: Float,
+ config: SpringConfig = defaultSpring
+ ): PhysicsAnimator<T> {
+ return spring(
+ property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
+ }
+
+ /**
+ * Springs a property to a given value using the provided configuration options, and a start
+ * velocity of 0f.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ config: SpringConfig = defaultSpring
+ ): PhysicsAnimator<T> {
+ return spring(property, toPosition, 0f, config)
+ }
+
+ /**
+ * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+ * the provided configuration settings.
+ *
+ * Flings are used when you have a start velocity, and want the property value to realistically
+ * decrease as friction is applied until the velocity reaches zero. Flings do not have a
+ * deterministic end value. If you are attempting to animate to a specific end value, use
+ * [spring].
+ *
+ * If you find yourself repeating the same friction/min/max values, consider storing a single
+ * [FlingConfig] and passing that in instead.
+ *
+ * @param property The property to fling using the given start velocity.
+ * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
+ * @param friction Friction value applied to slow down the animation over time. Higher values
+ * will more quickly slow the animation. Typical friction values range from 1f to 10f.
+ * @param min The minimum value allowed for the animation. If this value is reached, the
+ * animation will end abruptly.
+ * @param max The maximum value allowed for the animation. If this value is reached, the
+ * animation will end abruptly.
+ */
+ fun fling(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ friction: Float = defaultFling.friction,
+ min: Float = defaultFling.min,
+ max: Float = defaultFling.max
+ ): PhysicsAnimator<T> {
+ if (verboseLogging) {
+ Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
+ "with velocity $startVelocity.")
+ }
+
+ flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
+ return this
+ }
+
+ /**
+ * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+ * the provided configuration settings.
+ *
+ * @see fling
+ */
+ fun fling(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ config: FlingConfig = defaultFling
+ ): PhysicsAnimator<T> {
+ return fling(property, startVelocity, config.friction, config.min, config.max)
+ }
+
+ /**
+ * Adds a listener that will be called whenever any property on the animated object is updated.
+ * This will be called on every animation frame, with the current value of the animated object
+ * and the new property values.
+ */
+ fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
+ updateListeners.add(listener)
+ return this
+ }
+
+ /**
+ * Adds a listener that will be called whenever a property's animation ends. This is useful if
+ * you care about a specific property ending, or want to use the end value/end velocity from a
+ * particular property's animation. If you just want to run an action when all property
+ * animations have ended, use [withEndActions].
+ */
+ fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
+ endListeners.add(listener)
+ return this
+ }
+
+ /**
+ * Adds end actions that will be run sequentially when animations for every property involved in
+ * this specific animation have ended (unless they were explicitly canceled). For example, if
+ * you call:
+ *
+ * animator
+ * .spring(TRANSLATION_X, ...)
+ * .spring(TRANSLATION_Y, ...)
+ * .withEndAction(action)
+ * .start()
+ *
+ * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
+ *
+ * Other properties may still be animating, if those animations were not started in the same
+ * call. For example:
+ *
+ * animator
+ * .spring(ALPHA, ...)
+ * .start()
+ *
+ * animator
+ * .spring(TRANSLATION_X, ...)
+ * .spring(TRANSLATION_Y, ...)
+ * .withEndAction(action)
+ * .start()
+ *
+ * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
+ * still animating.
+ *
+ * If you want to run actions as soon as a subset of property animations have ended, you want
+ * access to the animation's end value/velocity, or you want to run these actions even if the
+ * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
+ * which indicates that all relevant animations have ended.
+ */
+ fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
+ this.endActions.addAll(endActions)
+ return this
+ }
+
+ /** Starts the animations! */
+ fun start() {
+ startAction()
+ }
+
+ /**
+ * Starts the animations for real! This is typically called immediately by [start] unless this
+ * animator is under test.
+ */
+ internal fun startInternal() {
+ if (!Looper.getMainLooper().isCurrentThread) {
+ Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
+ "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
+ "your test setup.")
+ }
+
+ // Add an internal listener that will dispatch animation events to the provided listeners.
+ internalListeners.add(InternalListener(
+ getAnimatedProperties(),
+ ArrayList(updateListeners),
+ ArrayList(endListeners),
+ ArrayList(endActions)))
+
+ for ((property, config) in flingConfigs) {
+ val currentValue = property.getValue(target)
+
+ // If the fling is already out of bounds, don't start it.
+ if (currentValue <= config.min || currentValue >= config.max) {
+ continue
+ }
+
+ val flingAnim = getFlingAnimation(property)
+ config.applyToAnimation(flingAnim)
+ flingAnim.start()
+ }
+
+ for ((property, config) in springConfigs) {
+ val springAnim = getSpringAnimation(property)
+ config.applyToAnimation(springAnim)
+ springAnim.start()
+ }
+
+ clearAnimator()
+ }
+
+ /** Clear the animator's builder variables. */
+ private fun clearAnimator() {
+ springConfigs.clear()
+ flingConfigs.clear()
+
+ updateListeners.clear()
+ endListeners.clear()
+ endActions.clear()
+ }
+
+ /** Retrieves a spring animation for the given property, building one if needed. */
+ private fun getSpringAnimation(property: FloatPropertyCompat<in T>): SpringAnimation {
+ return springAnimations.getOrPut(
+ property,
+ { configureDynamicAnimation(SpringAnimation(target, property), property)
+ as SpringAnimation })
+ }
+
+ /** Retrieves a fling animation for the given property, building one if needed. */
+ private fun getFlingAnimation(property: FloatPropertyCompat<in T>): FlingAnimation {
+ return flingAnimations.getOrPut(
+ property,
+ { configureDynamicAnimation(FlingAnimation(target, property), property)
+ as FlingAnimation })
+ }
+
+ /**
+ * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
+ * listeners.
+ */
+ private fun configureDynamicAnimation(
+ anim: DynamicAnimation<*>,
+ property: FloatPropertyCompat<in T>
+ ): DynamicAnimation<*> {
+ anim.addUpdateListener { _, value, velocity ->
+ for (i in 0 until internalListeners.size) {
+ internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
+ }
+ }
+ anim.addEndListener { _, canceled, value, velocity ->
+ internalListeners.removeAll {
+ it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ return anim
+ }
+
+ /**
+ * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
+ * them to the appropriate update/end listeners. This class is also aware of which properties
+ * were being animated when the end listeners were passed in, so that we can provide the
+ * appropriate value for allEnded to [EndListener.onAnimationEnd].
+ */
+ internal inner class InternalListener constructor(
+ private var properties: Set<FloatPropertyCompat<in T>>,
+ private var updateListeners: List<UpdateListener<T>>,
+ private var endListeners: List<EndListener<T>>,
+ private var endActions: List<EndAction>
+ ) {
+
+ /** The number of properties whose animations haven't ended. */
+ private var numPropertiesAnimating = properties.size
+
+ /**
+ * Update values that haven't yet been dispatched because not all property animations have
+ * updated yet.
+ */
+ private val undispatchedUpdates =
+ ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
+
+ /** Called when a DynamicAnimation updates. */
+ internal fun onInternalAnimationUpdate(
+ property: FloatPropertyCompat<in T>,
+ value: Float,
+ velocity: Float
+ ) {
+
+ // If this property animation isn't relevant to this listener, ignore it.
+ if (!properties.contains(property)) {
+ return
+ }
+
+ undispatchedUpdates[property] = AnimationUpdate(value, velocity)
+ maybeDispatchUpdates()
+ }
+
+ /**
+ * Called when a DynamicAnimation ends.
+ *
+ * @return True if this listener should be removed from the list of internal listeners, so
+ * it no longer receives updates from DynamicAnimations.
+ */
+ internal fun onInternalAnimationEnd(
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float
+ ): Boolean {
+
+ // If this property animation isn't relevant to this listener, ignore it.
+ if (!properties.contains(property)) {
+ return false
+ }
+
+ // Dispatch updates if we have one for each property.
+ numPropertiesAnimating--
+ maybeDispatchUpdates()
+
+ // If we didn't have an update for each property, dispatch the update for the ending
+ // property. This guarantees that an update isn't sent for this property *after* we call
+ // onAnimationEnd for that property.
+ if (undispatchedUpdates.contains(property)) {
+ updateListeners.forEach { updateListener ->
+ updateListener.onAnimationUpdateForProperty(
+ target,
+ UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
+ }
+
+ undispatchedUpdates.remove(property)
+ }
+
+ val allEnded = !arePropertiesAnimating(properties)
+ endListeners.forEach {
+ it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+
+ // If all of the animations that this listener cares about have ended, run the end
+ // actions unless the animation was canceled.
+ if (allEnded && !canceled) {
+ endActions.forEach { it() }
+ }
+
+ return allEnded
+ }
+
+ /**
+ * Dispatch undispatched values if we've received an update from each of the animating
+ * properties.
+ */
+ private fun maybeDispatchUpdates() {
+ if (undispatchedUpdates.size >= numPropertiesAnimating &&
+ undispatchedUpdates.size > 0) {
+ updateListeners.forEach {
+ it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
+ }
+
+ undispatchedUpdates.clear()
+ }
+ }
+ }
+
+ /** Return true if any animations are running on the object. */
+ fun isRunning(): Boolean {
+ return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
+ }
+
+ /** Returns whether the given property is animating. */
+ fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
+ return springAnimations[property]?.isRunning ?: false
+ }
+
+ /** Returns whether any of the given properties are animating. */
+ fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
+ return properties.any { isPropertyAnimating(it) }
+ }
+
+ /** Return the set of properties that will begin animating upon calling [start]. */
+ internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
+ return springConfigs.keys.union(flingConfigs.keys)
+ }
+
+ /** Cancels all in progress animations on all properties. */
+ fun cancel() {
+ for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) {
+ dynamicAnim.cancel()
+ }
+ }
+
+ /**
+ * Container object for spring animation configuration settings. This allows you to store
+ * default stiffness and damping ratio values in a single configuration object, which you can
+ * pass to [spring].
+ */
+ data class SpringConfig internal constructor(
+ internal var stiffness: Float,
+ internal var dampingRatio: Float,
+ internal var startVel: Float = 0f,
+ internal var finalPosition: Float = -Float.MAX_VALUE
+ ) {
+
+ constructor() :
+ this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+
+ constructor(stiffness: Float, dampingRatio: Float) :
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+
+ /** Apply these configuration settings to the given SpringAnimation. */
+ internal fun applyToAnimation(anim: SpringAnimation) {
+ val springForce = anim.spring ?: SpringForce()
+ anim.spring = springForce.apply {
+ stiffness = this@SpringConfig.stiffness
+ dampingRatio = this@SpringConfig.dampingRatio
+ finalPosition = this@SpringConfig.finalPosition
+ }
+
+ if (startVel != 0f) anim.setStartVelocity(startVel)
+ }
+ }
+
+ /**
+ * Container object for fling animation configuration settings. This allows you to store default
+ * friction values (as well as optional min/max values) in a single configuration object, which
+ * you can pass to [fling] and related methods.
+ */
+ data class FlingConfig internal constructor(
+ internal var friction: Float,
+ internal var min: Float,
+ internal var max: Float,
+ internal var startVel: Float
+ ) {
+
+ constructor() : this(defaultFling.friction)
+
+ constructor(friction: Float) :
+ this(friction, defaultFling.min, defaultFling.max)
+
+ constructor(friction: Float, min: Float, max: Float) :
+ this(friction, min, max, startVel = 0f)
+
+ /** Apply these configuration settings to the given FlingAnimation. */
+ internal fun applyToAnimation(anim: FlingAnimation) {
+ anim.apply {
+ friction = this@FlingConfig.friction
+ setMinValue(min)
+ setMaxValue(max)
+ setStartVelocity(startVel)
+ }
+ }
+ }
+
+ /**
+ * Listener for receiving values from in progress animations. Used with
+ * [PhysicsAnimator.addUpdateListener].
+ *
+ * @param <T> The type of the object being animated.
+ </T> */
+ interface UpdateListener<T> {
+
+ /**
+ * Called on each animation frame with the target object, and a map of FloatPropertyCompat
+ * -> AnimationUpdate, containing the latest value and velocity for that property. When
+ * multiple properties are animating together, the map will typically contain one entry for
+ * each property. However, you should never assume that this is the case - when a property
+ * animation ends earlier than the others, you'll receive an UpdateMap containing only that
+ * property's final update. Subsequently, you'll only receive updates for the properties
+ * that are still animating.
+ *
+ * Always check that the map contains an update for the property you're interested in before
+ * accessing it.
+ *
+ * @param target The animated object itself.
+ * @param values Map of property to AnimationUpdate, which contains that property
+ * animation's latest value and velocity. You should never assume that a particular property
+ * is present in this map.
+ */
+ fun onAnimationUpdateForProperty(
+ target: T,
+ values: UpdateMap<T>
+ )
+ }
+
+ /**
+ * Listener for receiving callbacks when animations end.
+ *
+ * @param <T> The type of the object being animated.
+ </T> */
+ interface EndListener<T> {
+
+ /**
+ * Called with the final animation values as each property animation ends. This can be used
+ * to respond to specific property animations concluding (such as hiding a view when ALPHA
+ * ends, even if the corresponding TRANSLATION animations have not ended).
+ *
+ * If you just want to run an action when all of the property animations have ended, you can
+ * use [PhysicsAnimator.withEndActions].
+ *
+ * @param target The animated object itself.
+ * @param property The property whose animation has just ended.
+ * @param canceled Whether the animation was explicitly canceled before it naturally ended.
+ * @param finalValue The final value of the animated property.
+ * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
+ * This is typically zero, unless this was a fling animation which ended abruptly due to
+ * reaching its configured min/max values.
+ * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
+ * have ended. Relevant properties are those which were animated alongside the
+ * [addEndListener] call where this animator was passed in. For example:
+ *
+ * animator
+ * .spring(TRANSLATION_X, 100f)
+ * .spring(TRANSLATION_Y, 200f)
+ * .withEndListener(firstEndListener)
+ * .start()
+ *
+ * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
+ * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
+ * allEnded = true.
+ *
+ * If a subsequent call to start() is made with other properties, those properties are not
+ * considered relevant and allEnded will still equal true when only TRANSLATION_X and
+ * TRANSLATION_Y end. For example, if immediately after the prior example, while
+ * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
+ *
+ * animator.
+ * .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
+ * .withEndListener(secondEndListener)
+ * .start()
+ *
+ * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
+ * though SCALE_X is still animating. Similarly, secondEndListener will be called with
+ * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
+ * running.
+ */
+ fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ )
+ }
+
+ companion object {
+
+ /**
+ * Constructor to use to for new physics animator instances in [getInstance]. This is
+ * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
+ * all code using the physics animator is given testable instances instead.
+ */
+ internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+
+ @JvmStatic
+ fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
+ if (!animators.containsKey(target)) {
+ animators[target] = instanceConstructor(target)
+ }
+
+ return animators[target] as PhysicsAnimator<T>
+ }
+
+ /**
+ * Set whether all physics animators should log a lot of information about animations.
+ * Useful for debugging!
+ */
+ @JvmStatic
+ fun setVerboseLogging(debug: Boolean) {
+ verboseLogging = debug
+ }
+
+ @JvmStatic
+ fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
+ return when (property) {
+ DynamicAnimation.TRANSLATION_X -> "translationX"
+ DynamicAnimation.TRANSLATION_Y -> "translationY"
+ DynamicAnimation.TRANSLATION_Z -> "translationZ"
+ DynamicAnimation.SCALE_X -> "scaleX"
+ DynamicAnimation.SCALE_Y -> "scaleY"
+ DynamicAnimation.ROTATION -> "rotation"
+ DynamicAnimation.ROTATION_X -> "rotationX"
+ DynamicAnimation.ROTATION_Y -> "rotationY"
+ DynamicAnimation.SCROLL_X -> "scrollX"
+ DynamicAnimation.SCROLL_Y -> "scrollY"
+ DynamicAnimation.ALPHA -> "alpha"
+ else -> "Custom FloatPropertyCompat instance"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
new file mode 100644
index 000000000000..a1f74eb40cad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -0,0 +1,457 @@
+/*
+ * 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.util.animation
+
+import android.os.Handler
+import android.os.Looper
+import android.util.ArrayMap
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import java.util.ArrayDeque
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
+typealias UpdateFramesPerProperty<T> =
+ ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
+
+/**
+ * Utilities for testing code that uses [PhysicsAnimator].
+ *
+ * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
+ * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
+ * crash). It'll also enable the use of the other static helper methods in this class, which you can
+ * use to do things like block the test until animations complete (so you can test end states), or
+ * verify keyframes.
+ */
+object PhysicsAnimatorTestUtils {
+ var timeoutMs: Long = 2000
+ private var startBlocksUntilAnimationsEnd = false
+ private val animationThreadHandler = Handler(Looper.getMainLooper())
+ private val allAnimatedObjects = HashSet<Any>()
+ private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
+
+ /**
+ * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
+ * main thread, and report all of their
+ */
+ @JvmStatic
+ fun prepareForTest() {
+ val defaultConstructor = PhysicsAnimator.instanceConstructor
+ PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
+ val animator = defaultConstructor(target)
+ allAnimatedObjects.add(target)
+ animatorTestHelpers[animator] = AnimatorTestHelper(animator)
+ return animator
+ }
+
+ timeoutMs = 2000
+ startBlocksUntilAnimationsEnd = false
+ allAnimatedObjects.clear()
+ }
+
+ @JvmStatic
+ fun tearDown() {
+ val latch = CountDownLatch(1)
+ animationThreadHandler.post {
+ animatorTestHelpers.keys.forEach { it.cancel() }
+ latch.countDown()
+ }
+
+ latch.await()
+
+ animatorTestHelpers.clear()
+ animators.clear()
+ allAnimatedObjects.clear()
+ }
+
+ /**
+ * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
+ * before throwing an exception.
+ */
+ @JvmStatic
+ fun setBlockTimeout(timeoutMs: Long) {
+ this.timeoutMs = timeoutMs
+ }
+
+ /**
+ * Sets whether all animations should block the test thread until they end. This is typically
+ * the desired behavior, since you can invoke code that runs an animation and then assert things
+ * about its end state.
+ */
+ @JvmStatic
+ fun setAllAnimationsBlock(block: Boolean) {
+ startBlocksUntilAnimationsEnd = block
+ }
+
+ /**
+ * Blocks the calling thread until animations of the given property on the target object end.
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilAnimationsEnd(
+ animator: PhysicsAnimator<T>,
+ vararg properties: FloatPropertyCompat<in T>
+ ) {
+ val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
+ for (property in properties) {
+ if (animator.isPropertyAnimating(property)) {
+ animatingProperties.add(property)
+ }
+ }
+
+ if (animatingProperties.size > 0) {
+ val latch = CountDownLatch(animatingProperties.size)
+ getAnimationTestHelper(animator).addTestEndListener(
+ object : PhysicsAnimator.EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ if (animatingProperties.contains(property)) {
+ latch.countDown()
+ }
+ }
+ })
+
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ /**
+ * Blocks the calling thread until all animations of the given property (on all target objects)
+ * have ended. Useful when you don't have access to the objects being animated, but still need
+ * to wait for them to end so that other testable side effects occur (such as update/end
+ * listeners).
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilAnimationsEnd(
+ properties: FloatPropertyCompat<in T>
+ ) {
+ for (target in allAnimatedObjects) {
+ try {
+ blockUntilAnimationsEnd(
+ PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+ } catch (e: ClassCastException) {
+ // Keep checking the other objects for ones whose types match the provided
+ // properties.
+ }
+ }
+ }
+
+ /**
+ * Blocks the calling thread until the first animation frame in which predicate returns true. If
+ * the given object isn't animating, returns without blocking.
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
+ animator: PhysicsAnimator<T>,
+ predicate: (T) -> Boolean
+ ) {
+ if (animator.isRunning()) {
+ val latch = CountDownLatch(1)
+ getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
+ .UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: UpdateMap<T>
+ ) {
+ if (predicate(target)) {
+ latch.countDown()
+ }
+ }
+ })
+
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ /**
+ * Verifies that the animator reported animation frame values to update listeners that satisfy
+ * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
+ * all animation frames, and check them against the current predicate. If it returns false, we
+ * continue through the frames until it returns true, and then move on to the next matcher.
+ * Verification fails if we run out of frames while unsatisfied matchers remain.
+ *
+ * If verification is successful, all frames to this point are considered 'verified' and will be
+ * cleared. Subsequent calls to this method will start verification at the next animation frame.
+ *
+ * Example: Verify that an animation surpassed x = 50f before going negative.
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 50f },
+ * { u -> u.value < 0f })
+ *
+ * Example: verify that an animation went backwards at some point while still being on-screen.
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.velocity < 0f && u.value >= 0f })
+ *
+ * This method is intended to help you test longer, more complicated animations where it's
+ * critical that certain values were reached. Using this method to test short animations can
+ * fail due to the animation having fewer frames than provided matchers. For example, an
+ * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
+ * following would then fail despite it seeming logically sound:
+ *
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 1f },
+ * { u -> u.value > 2f },
+ * { u -> u.value > 3f })
+ *
+ * Tests might also fail if your matchers are too granular, such as this example test after an
+ * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
+ * and 3f.
+ *
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 2f && u.value < 3f },
+ * { u -> u.value >= 50f })
+ *
+ * Failures will print a helpful log of all animation frames so you can see what caused the test
+ * to fail.
+ */
+ fun <T : Any> verifyAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>,
+ property: FloatPropertyCompat<in T>,
+ firstUpdateMatcher: UpdateMatcher,
+ vararg additionalUpdateMatchers: UpdateMatcher
+ ) {
+ val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
+ val matchers = ArrayDeque<UpdateMatcher>(
+ additionalUpdateMatchers.toList())
+ val frameTraceMessage = StringBuilder()
+
+ var curMatcher = firstUpdateMatcher
+
+ // Loop through the updates from the testable animator.
+ for (update in updateFrames[property]
+ ?: error("No frames for given target object and property.")) {
+
+ // Check whether this frame satisfies the current matcher.
+ if (curMatcher(update)) {
+
+ // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
+ // frames and return without failing.
+ if (matchers.size == 0) {
+ getAnimationUpdateFrames(animator).remove(property)
+ return
+ }
+
+ frameTraceMessage.append("$update\t(satisfied matcher)\n")
+ curMatcher = matchers.pop() // Get the next matcher and keep going.
+ } else {
+ frameTraceMessage.append("${update}\n")
+ }
+ }
+
+ val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
+ getAnimationUpdateFrames(animator).remove(property)
+
+ throw RuntimeException(
+ "Failed to verify animation frames for property $readablePropertyName: " +
+ "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
+ "however ${matchers.size + 1} remained unsatisfied.\n\n" +
+ "All frames:\n$frameTraceMessage")
+ }
+
+ /**
+ * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
+ * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
+ *
+ * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
+ *
+ * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
+ * <= 50f.
+ *
+ * The same caveats apply: short animations might not have enough frames to satisfy all of the
+ * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
+ * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
+ * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
+ * so you can see what caused the test to fail.
+ */
+ fun <T : Any> verifyAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>,
+ property: FloatPropertyCompat<in T>,
+ startValue: Float,
+ firstTargetValue: Float,
+ vararg additionalTargetValues: Float
+ ) {
+ val matchers = ArrayList<UpdateMatcher>()
+
+ val values = ArrayList<Float>().also {
+ it.add(firstTargetValue)
+ it.addAll(additionalTargetValues.toList())
+ }
+
+ var prevVal = startValue
+ for (value in values) {
+ if (value > prevVal) {
+ matchers.add { update -> update.value >= value }
+ } else {
+ matchers.add { update -> update.value <= value }
+ }
+
+ prevVal = value
+ }
+
+ verifyAnimationUpdateFrames(
+ animator, property, matchers[0], *matchers.drop(0).toTypedArray())
+ }
+
+ /**
+ * Returns all of the values that have ever been reported to update listeners, per property.
+ */
+ fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
+ UpdateFramesPerProperty<T> {
+ return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
+ }
+
+ /**
+ * Clears animation frame updates from the given animator so they aren't used the next time its
+ * passed to [verifyAnimationUpdateFrames].
+ */
+ fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
+ animatorTestHelpers[animator]?.clearUpdates()
+ }
+
+ private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
+ return animatorTestHelpers[animator] as AnimatorTestHelper<T>
+ }
+
+ /**
+ * Helper class for testing an animator. This replaces the animator's start action with
+ * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
+ * these for each Animator and keep them around so we can access the updates.
+ */
+ class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
+
+ /** All updates received for each property animation. */
+ private val allUpdates =
+ ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
+
+ private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
+ private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
+
+ init {
+ animator.startAction = ::startForTest
+ }
+
+ internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
+ testEndListeners.add(listener)
+ }
+
+ internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
+ testUpdateListeners.add(listener)
+ }
+
+ internal fun getUpdates(): UpdateFramesPerProperty<T> {
+ return allUpdates
+ }
+
+ internal fun clearUpdates() {
+ allUpdates.clear()
+ }
+
+ private fun startForTest() {
+ // The testable animator needs to block the main thread until super.start() has been
+ // called, since callers expect .start() to be synchronous but we're posting it to a
+ // handler here. We may also continue blocking until all animations end, if
+ // startBlocksUntilAnimationsEnd = true.
+ val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
+
+ animationThreadHandler.post {
+ val animatedProperties = animator.getAnimatedProperties()
+
+ // Add an update listener that dispatches to any test update listeners added by
+ // tests.
+ animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+ ) {
+ for (listener in testUpdateListeners) {
+ listener.onAnimationUpdateForProperty(target, values)
+ }
+ }
+ })
+
+ // Add an end listener that dispatches to any test end listeners added by tests, and
+ // unblocks the main thread if required.
+ animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ for (listener in testEndListeners) {
+ listener.onAnimationEnd(
+ target, property, canceled, finalValue, finalVelocity,
+ allRelevantPropertyAnimsEnded)
+ }
+
+ if (allRelevantPropertyAnimsEnded) {
+ testEndListeners.clear()
+ testUpdateListeners.clear()
+
+ if (startBlocksUntilAnimationsEnd) {
+ unblockLatch.countDown()
+ }
+ }
+ }
+ })
+
+ val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
+ it.add(object : PhysicsAnimator.UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: ArrayMap<FloatPropertyCompat<in T>,
+ PhysicsAnimator.AnimationUpdate>
+ ) {
+ values.forEach { (property, value) ->
+ allUpdates.getOrPut(property, { ArrayList() }).add(value)
+ }
+ }
+ })
+ }
+
+ /**
+ * Add an internal listener at the head of the list that captures update values
+ * directly from DynamicAnimation. We use this to build a list of all updates so we
+ * can verify that InternalListener dispatches to the real listeners properly.
+ */
+ animator.internalListeners.add(0, animator.InternalListener(
+ animatedProperties,
+ updateListeners,
+ ArrayList(),
+ ArrayList()))
+
+ animator.startInternal()
+ unblockLatch.countDown()
+ }
+
+ unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 24f49ff99879..3e90581292ce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -18,11 +18,12 @@ package com.android.systemui.util.concurrency;
import android.content.Context;
import android.os.Handler;
+import android.os.Looper;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.BgLooper;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
import java.util.concurrent.Executor;
@@ -38,8 +39,8 @@ public abstract class ConcurrencyModule {
* Provide a Background-Thread Executor by default.
*/
@Provides
- public static Executor provideExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static Executor provideExecutor(@BgLooper Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -47,8 +48,8 @@ public abstract class ConcurrencyModule {
*/
@Provides
@Background
- public static Executor provideBackgroundExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static Executor provideBackgroundExecutor(@BgLooper Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -64,8 +65,8 @@ public abstract class ConcurrencyModule {
* Provide a Background-Thread Executor by default.
*/
@Provides
- public static DelayableExecutor provideDelayableExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static DelayableExecutor provideDelayableExecutor(@BgLooper Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -73,8 +74,8 @@ public abstract class ConcurrencyModule {
*/
@Provides
@Background
- public static DelayableExecutor provideBackgroundDelayableExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static DelayableExecutor provideBackgroundDelayableExecutor(@BgLooper Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -82,7 +83,7 @@ public abstract class ConcurrencyModule {
*/
@Provides
@Main
- public static DelayableExecutor provideMainDelayableExecutor(@MainHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static DelayableExecutor provideMainDelayableExecutor(@MainLooper Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 4cb54728a64c..9c9a627fa6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -46,11 +46,11 @@ import androidx.test.filters.SmallTest;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import junit.framework.Assert;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
index 212c93dc576d..46a473bd6543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -27,8 +27,8 @@ import android.app.Notification;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2ccececbdd9d..e0b4b81c368d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -45,9 +45,7 @@ import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.drawable.Icon;
import android.hardware.face.FaceManager;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
@@ -57,7 +55,6 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -261,7 +258,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testRemoveBubble_withDismissedNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -304,7 +301,7 @@ public class BubbleControllerTest extends SysuiTestCase {
assertFalse(mBubbleController.isStackExpanded());
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -334,8 +331,8 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testCollapseAfterChangingExpandedBubble() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
@@ -377,7 +374,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testExpansionRemovesShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -403,7 +400,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -439,8 +436,8 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testRemoveLastExpandedCollapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -483,7 +480,7 @@ public class BubbleControllerTest extends SysuiTestCase {
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Expansion shouldn't change
@@ -501,7 +498,7 @@ public class BubbleControllerTest extends SysuiTestCase {
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Expansion should change
@@ -519,7 +516,7 @@ public class BubbleControllerTest extends SysuiTestCase {
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
// Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Notif should be suppressed because we were foreground
@@ -564,7 +561,7 @@ public class BubbleControllerTest extends SysuiTestCase {
public void testExpandStackAndSelectBubble_removedFirst() {
final String key = mRow.getEntry().getKey();
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Simulate notification cancellation.
@@ -576,7 +573,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry().getKey()));
@@ -586,7 +583,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testAddNotif_notBubble() {
- mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry());
mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
@@ -631,7 +628,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testRemoveBubble_succeeds_appCancel() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -646,7 +643,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void removeBubble_fails_clearAll() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -669,7 +666,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void removeBubble_fails_userDismissNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -692,7 +689,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -731,6 +728,7 @@ public class BubbleControllerTest extends SysuiTestCase {
data, Runnable::run, configurationController, interruptionStateProvider,
zenModeController, lockscreenUserManager, groupManager, entryManager,
remoteInputUriController);
+ setInflateSynchronously(true);
}
}
@@ -746,17 +744,6 @@ public class BubbleControllerTest extends SysuiTestCase {
}
/**
- * @return basic {@link android.app.Notification.BubbleMetadata.Builder}
- */
- private Notification.BubbleMetadata.Builder getBuilder() {
- Intent target = new Intent(mContext, BubblesTestActivity.class);
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
- return new Notification.BubbleMetadata.Builder()
- .setIntent(bubbleIntent)
- .setIcon(Icon.createWithResource(mContext, R.drawable.android));
- }
-
- /**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
* go through that path so we set them explicitly when testing.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 1554abcec3b2..c4ae4093b680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -39,9 +39,9 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleData.TimeSource;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.google.common.collect.ImmutableList;
@@ -85,6 +85,8 @@ public class BubbleDataTest extends SysuiTestCase {
private Bubble mBubbleB2;
private Bubble mBubbleB3;
private Bubble mBubbleC1;
+ private Bubble mBubbleInterruptive;
+ private Bubble mBubbleDismissed;
private BubbleData mBubbleData;
@@ -119,18 +121,20 @@ public class BubbleDataTest extends SysuiTestCase {
modifyRanking(mEntryInterruptive)
.setVisuallyInterruptive(true)
.build();
+ mBubbleInterruptive = new Bubble(mEntryInterruptive);
ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
mEntryDismissed.setRow(row);
+ mBubbleDismissed = new Bubble(mEntryDismissed);
- mBubbleA1 = new Bubble(mContext, mEntryA1);
- mBubbleA2 = new Bubble(mContext, mEntryA2);
- mBubbleA3 = new Bubble(mContext, mEntryA3);
- mBubbleB1 = new Bubble(mContext, mEntryB1);
- mBubbleB2 = new Bubble(mContext, mEntryB2);
- mBubbleB3 = new Bubble(mContext, mEntryB3);
- mBubbleC1 = new Bubble(mContext, mEntryC1);
+ mBubbleA1 = new Bubble(mEntryA1);
+ mBubbleA2 = new Bubble(mEntryA2);
+ mBubbleA3 = new Bubble(mEntryA3);
+ mBubbleB1 = new Bubble(mEntryB1);
+ mBubbleB2 = new Bubble(mEntryB2);
+ mBubbleB3 = new Bubble(mEntryB3);
+ mBubbleC1 = new Bubble(mEntryC1);
mBubbleData = new BubbleData(getContext());
@@ -180,7 +184,7 @@ public class BubbleDataTest extends SysuiTestCase {
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ true, /* showInShade */
+ mBubbleData.notificationEntryUpdated(mBubbleC1, /* suppressFlyout */ true, /* showInShade */
true);
// Verify
@@ -195,7 +199,7 @@ public class BubbleDataTest extends SysuiTestCase {
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryInterruptive,
+ mBubbleData.notificationEntryUpdated(mBubbleInterruptive,
false /* suppressFlyout */, true /* showInShade */);
// Verify
@@ -210,11 +214,11 @@ public class BubbleDataTest extends SysuiTestCase {
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryC1, false /* suppressFlyout */,
+ mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
true /* showInShade */);
verifyUpdateReceived();
- mBubbleData.notificationEntryUpdated(mEntryC1, false /* suppressFlyout */,
+ mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
true /* showInShade */);
verifyUpdateReceived();
@@ -229,16 +233,16 @@ public class BubbleDataTest extends SysuiTestCase {
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryDismissed, false /* suppressFlyout */,
- false /* showInShade */);
+ mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
+ true /* showInShade */);
verifyUpdateReceived();
// Make it look like user swiped away row
mEntryDismissed.getRow().dismiss(false /* refocusOnDismiss */);
- assertThat(mBubbleData.getBubbleWithKey(mEntryDismissed.getKey()).showInShade()).isFalse();
+ assertThat(mBubbleData.getBubbleWithKey(mBubbleDismissed.getKey()).showInShade()).isFalse();
- mBubbleData.notificationEntryUpdated(mEntryDismissed, false /* suppressFlyout */,
- false /* showInShade */);
+ mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
+ true /* showInShade */);
verifyUpdateReceived();
// Verify
@@ -974,7 +978,10 @@ public class BubbleDataTest extends SysuiTestCase {
private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
setPostTime(entry, postTime);
- mBubbleData.notificationEntryUpdated(entry, false /* suppressFlyout*/,
+ // BubbleController calls this:
+ Bubble b = mBubbleData.getOrCreateBubble(entry);
+ // And then this
+ mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
true /* showInShade */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index 57578611e3f2..3c42fd1cb48d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -28,8 +28,8 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -58,7 +58,7 @@ public class BubbleTest extends SysuiTestCase {
mEntry = new NotificationEntryBuilder()
.setNotification(mNotif)
.build();
- mBubble = new Bubble(mContext, mEntry);
+ mBubble = new Bubble(mEntry);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index f2665ef3c845..775acdf58c64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.database.ContentObserver;
+import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -80,6 +81,8 @@ public class DozeSensorsTest extends SysuiTestCase {
private TriggerSensor mTriggerSensor;
@Mock
private DozeLog mDozeLog;
+ @Mock
+ private Sensor mProximitySensor;
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
private DozeSensors mDozeSensors;
@@ -90,6 +93,7 @@ public class DozeSensorsTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -98,6 +102,14 @@ public class DozeSensorsTest extends SysuiTestCase {
}
@Test
+ public void testRegisterProx() {
+ // We should not register with the sensor manager initially.
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
+ mDozeSensors.setProxListening(true);
+ verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
+ }
+
+ @Test
public void testSensorDebounce() {
mDozeSensors.setListening(true);
@@ -116,6 +128,7 @@ public class DozeSensorsTest extends SysuiTestCase {
@Test
public void testSetListening_firstTrue_registerSettingsObserver() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
@@ -123,6 +136,7 @@ public class DozeSensorsTest extends SysuiTestCase {
@Test
public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
mDozeSensors.setListening(true);
@@ -131,6 +145,7 @@ public class DozeSensorsTest extends SysuiTestCase {
@Test
public void testSetPaused_doesntPause_sensors() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
verify(mTriggerSensor).setListening(eq(true));
@@ -147,8 +162,7 @@ public class DozeSensorsTest extends SysuiTestCase {
TestableDozeSensors() {
super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
- mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback,
- mAlwaysOnDisplayPolicy, mDozeLog);
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
for (TriggerSensor sensor : mSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 800456228e84..b7e9f074ad9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -31,6 +31,7 @@ import android.view.View;
import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
+import androidx.test.filters.Suppress;
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.CarrierText;
@@ -64,7 +65,7 @@ import java.util.Optional;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
-@Ignore
+@Suppress
public class QSFragmentTest extends SysuiBaseFragmentTest {
private MetricsLogger mMockMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index c6dd23262b85..a5395e8ebe86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index 9b860c9e0ba7..677a6fc6b46e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -52,6 +52,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 8e6f4d7dd793..d580234725ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -27,7 +27,8 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.os.Handler;
import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -35,8 +36,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
import org.junit.Before;
import org.junit.Test;
@@ -51,52 +51,39 @@ public class NotificationListenerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- @Mock private NotificationListenerService.RankingMap mRanking;
-
- // Dependency mocks:
- @Mock private NotificationEntryManager mEntryManager;
+ @Mock private NotifServiceListener mServiceListener;
@Mock private NotificationManager mNotificationManager;
- @Mock private NotificationGroupManager mNotificationGroupManager;
private NotificationListener mListener;
private StatusBarNotification mSbn;
+ private RankingMap mRanking = new RankingMap(new Ranking[0]);
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
- mListener = new NotificationListener(mContext,
- new Handler(TestableLooper.get(this).getLooper()), mEntryManager,
- mNotificationGroupManager);
+ mListener = new NotificationListener(
+ mContext,
+ mNotificationManager,
+ new Handler(TestableLooper.get(this).getLooper()));
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
- }
- @Test
- public void testNotificationAddCallsAddNotification() {
- mListener.onNotificationPosted(mSbn, mRanking);
- TestableLooper.get(this).processAllMessages();
- verify(mEntryManager).addNotification(mSbn, mRanking);
+ mListener.addNotificationListener(mServiceListener);
}
@Test
- public void testNotificationUpdateCallsUpdateNotification() {
- when(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()))
- .thenReturn(
- new NotificationEntryBuilder()
- .setSbn(mSbn)
- .build());
+ public void testNotificationAddCallsAddNotification() {
mListener.onNotificationPosted(mSbn, mRanking);
TestableLooper.get(this).processAllMessages();
- verify(mEntryManager).updateNotification(mSbn, mRanking);
+ verify(mServiceListener).onNotificationPosted(mSbn, mRanking);
}
@Test
public void testNotificationRemovalCallsRemoveNotification() {
mListener.onNotificationRemoved(mSbn, mRanking);
TestableLooper.get(this).processAllMessages();
- verify(mEntryManager).removeNotification(eq(mSbn.getKey()), eq(mRanking), anyInt());
+ verify(mServiceListener).onNotificationRemoved(eq(mSbn), eq(mRanking), anyInt());
}
@Test
@@ -104,7 +91,7 @@ public class NotificationListenerTest extends SysuiTestCase {
mListener.onNotificationRankingUpdate(mRanking);
TestableLooper.get(this).processAllMessages();
// RankingMap may be modified by plugins.
- verify(mEntryManager).updateNotificationRanking(any());
+ verify(mServiceListener).onNotificationRankingUpdate(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index cef210cef19a..3a6accea2b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -56,6 +56,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 8aac1891a5e4..64b10c816da7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInput
import com.android.systemui.statusbar.NotificationRemoteInputManager.SmartReplyHistoryExtender;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 88546b9b31a7..c7810be5128e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -44,6 +44,7 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationContentInflaterTest;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 07d2e3128d87..1b05216ac6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 3f624128ac2c..22dc0803339c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -41,6 +41,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index e0dfe7e66ad5..7f5105ee9310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -66,7 +66,6 @@ import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -80,6 +79,7 @@ import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 7fabb0fee8e2..5aed61b98ad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -41,11 +41,11 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 133d52b4f946..7343e5e9e07d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -30,9 +30,9 @@ import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index 6a4ddc7ec202..9079223649ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -31,9 +31,9 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
index 721bbdc1f700..a06d6c1e593b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
@@ -27,7 +27,6 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.RankingBuilder;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index e1beb34db6bd..0dcd25300633 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -48,7 +48,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
import com.android.systemui.statusbar.RankingBuilder;
@@ -99,7 +98,7 @@ public class NotifCollectionTest extends SysuiTestCase {
// Capture the listener object that the collection registers with the listener service so
// we can simulate listener service events in tests below
- verify(mListenerService).setDownstreamListener(mListenerCaptor.capture());
+ verify(mListenerService).addNotificationListener(mListenerCaptor.capture());
mServiceListener = checkNotNull(mListenerCaptor.getValue());
mNoMan = new NoManSimulator(mServiceListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
index bbabb1154e03..9f90396e08d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
@@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -38,7 +39,6 @@ import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -336,7 +336,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
// GIVEN a notification that is initially added to the list
PackageFilter filter = new PackageFilter(PACKAGE_2);
filter.setEnabled(false);
- mListBuilder.addFilter(filter);
+ mListBuilder.addPreGroupFilter(filter);
addNotif(0, PACKAGE_1);
addNotif(1, PACKAGE_2);
@@ -373,24 +373,54 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
notif(2)
);
- // THEN the list of newly visible entries doesn't contain the summary or the group
- assertEquals(
- Arrays.asList(
- mEntrySet.get(0),
- mEntrySet.get(2)),
- listener.newlyVisibleEntries
- );
-
// THEN the summary has a null parent and an unset firstAddedIteration
assertNull(mEntrySet.get(1).getParent());
assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
}
@Test
- public void testNotifsAreFiltered() {
+ public void testPreGroupNotifsAreFiltered() {
+ // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
+ NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
+ NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
+ mListBuilder.addPreGroupFilter(preGroupFilter);
+ mListBuilder.addPreRenderFilter(preRenderFilter);
+
+ // WHEN the pipeline is kicked off on a list of notifs
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ addNotif(2, PACKAGE_3);
+ addNotif(3, PACKAGE_2);
+ dispatchBuild();
+
+ // THEN the preGroupFilter is called on each notif in the original set
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+ // THEN the preRenderFilter is only called on the notifications not already filtered out
+ verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+ verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+ verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+ verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+ // THEN the final list doesn't contain any filtered-out notifs
+ verifyBuiltList(
+ notif(0),
+ notif(2)
+ );
+
+ // THEN each filtered notif records the NotifFilter that did it
+ assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
+ assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+ }
+
+ @Test
+ public void testPreRenderNotifsAreFiltered() {
// GIVEN a NotifFilter that filters out a specific package
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
- mListBuilder.addFilter(filter1);
+ mListBuilder.addPreRenderFilter(filter1);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -421,8 +451,8 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
- mListBuilder.addFilter(filter1);
- mListBuilder.addFilter(filter2);
+ mListBuilder.addPreGroupFilter(filter1);
+ mListBuilder.addPreGroupFilter(filter2);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -522,7 +552,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
public void testNotifsAreSectioned() {
// GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
// notifs based on package name
- mListBuilder.addFilter(new PackageFilter(PACKAGE_4));
+ mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
final SectionsProvider sectionsProvider = spy(new PackageSectioner());
mListBuilder.setSectionsProvider(sectionsProvider);
@@ -596,17 +626,19 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
@Test
public void testListenersAndPluggablesAreFiredInOrder() {
// GIVEN a bunch of registered listeners and pluggables
- NotifFilter filter = spy(new PackageFilter(PACKAGE_1));
+ NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
PackageSectioner sectioner = spy(new PackageSectioner());
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
- mListBuilder.addFilter(filter);
+ NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
+ mListBuilder.addPreGroupFilter(preGroupFilter);
mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
mListBuilder.setSectionsProvider(sectioner);
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
+ mListBuilder.addPreRenderFilter(preRenderFilter);
// WHEN a few new notifs are added
addNotif(0, PACKAGE_1);
@@ -620,25 +652,28 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
// THEN the pluggables and listeners are called in order
InOrder inOrder = inOrder(
- filter,
+ preGroupFilter,
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
sectioner,
comparator,
+ preRenderFilter,
mOnBeforeRenderListListener,
mOnRenderListListener);
- inOrder.verify(filter, atLeastOnce())
+ inOrder.verify(preGroupFilter, atLeastOnce())
.shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeTransformGroupsListener)
- .onBeforeTransformGroups(anyList(), anyList());
+ .onBeforeTransformGroups(anyList());
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
+ inOrder.verify(preRenderFilter, atLeastOnce())
+ .shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
inOrder.verify(mOnRenderListListener).onRenderList(anyList());
}
@@ -651,7 +686,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
SectionsProvider sectionsProvider = new PackageSectioner();
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
- mListBuilder.addFilter(packageFilter);
+ mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
mListBuilder.setSectionsProvider(sectionsProvider);
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
@@ -687,9 +722,9 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
- mListBuilder.addFilter(filter1);
- mListBuilder.addFilter(filter2);
- mListBuilder.addFilter(filter3);
+ mListBuilder.addPreGroupFilter(filter1);
+ mListBuilder.addPreGroupFilter(filter2);
+ mListBuilder.addPreGroupFilter(filter3);
// GIVEN the SystemClock is set to a particular time:
mSystemClock.setUptimeMillis(47);
@@ -709,13 +744,13 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
}
@Test
- public void testNewlyAddedEntries() {
+ public void testGroupTransformEntries() {
// GIVEN a registered OnBeforeTransformGroupsListener
RecordingOnBeforeTransformGroupsListener listener =
spy(new RecordingOnBeforeTransformGroupsListener());
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // Given some new notifs
+ // GIVEN some new notifs
addNotif(0, PACKAGE_1);
addGroupChild(1, PACKAGE_2, GROUP_1);
addGroupSummary(2, PACKAGE_2, GROUP_1);
@@ -743,27 +778,18 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
mEntrySet.get(0),
mBuiltList.get(1),
mEntrySet.get(4)
- ),
- Arrays.asList(
- mEntrySet.get(0),
- mEntrySet.get(1),
- mBuiltList.get(1),
- mEntrySet.get(2),
- mEntrySet.get(3),
- mEntrySet.get(4),
- mEntrySet.get(5)
)
);
}
@Test
- public void testNewlyAddedEntriesOnSecondRun() {
+ public void testGroupTransformEntriesOnSecondRun() {
// GIVEN a registered OnBeforeTransformGroupsListener
RecordingOnBeforeTransformGroupsListener listener =
spy(new RecordingOnBeforeTransformGroupsListener());
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // Given some notifs that have already been added (two of which are in malformed groups)
+ // GIVEN some notifs that have already been added (two of which are in malformed groups)
addNotif(0, PACKAGE_1);
addGroupChild(1, PACKAGE_2, GROUP_1);
addGroupChild(2, PACKAGE_3, GROUP_2);
@@ -799,13 +825,6 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
mEntrySet.get(1),
mBuiltList.get(2),
mEntrySet.get(7)
- ),
- Arrays.asList(
- mBuiltList.get(2),
- mEntrySet.get(4),
- mEntrySet.get(5),
- mEntrySet.get(6),
- mEntrySet.get(7)
)
);
}
@@ -842,19 +861,18 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderFilterInvalidationThrows() {
- // GIVEN a NotifFilter that gets invalidated during the grouping stage
+ public void testOutOfOrderPreGroupFilterInvalidationThrows() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener =
- (list, newlyVisibleEntries) -> filter.invalidateList();
- mListBuilder.addFilter(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
// WHEN we try to run the pipeline and the filter is invalidated
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
}
@Test(expected = IllegalStateException.class)
@@ -870,7 +888,7 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
}
@Test(expected = IllegalStateException.class)
@@ -886,7 +904,37 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOutOfOrderPreRenderFilterInvalidationThrows() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
+ NotifFilter filter = new PackageFilter(PACKAGE_5);
+ OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreRenderFilter(filter);
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testInOrderPreRenderFilter() {
+ // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
+ NotifFilter filter = new PackageFilter(PACKAGE_5);
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreRenderFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN no exception thrown
}
/**
@@ -1178,13 +1226,9 @@ public class NotifListBuilderImplTest extends SysuiTestCase {
private static class RecordingOnBeforeTransformGroupsListener
implements OnBeforeTransformGroupsListener {
- public List<ListEntry> newlyVisibleEntries;
@Override
- public void onBeforeTransformGroups(List<ListEntry> list,
- List<ListEntry> newlyVisibleEntries) {
- this.newlyVisibleEntries = newlyVisibleEntries;
- }
+ public void onBeforeTransformGroups(List<ListEntry> list) { }
}
private static final String PACKAGE_1 = "com.test1";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index d18b16be33d6..e6a61d62a5b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -14,18 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification.collection;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager.Importance;
+import android.app.NotificationManager;
import android.content.Context;
import android.os.UserHandle;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SbnBuilder;
import java.util.ArrayList;
@@ -34,6 +35,8 @@ import java.util.ArrayList;
* and Ranking. Is largely a proxy for the SBN and Ranking builders, but does a little extra magic
* to make sure the keys match between the two, etc.
*
+ * Has the ability to set ListEntry properties as well.
+ *
* Only for use in tests.
*/
public class NotificationEntryBuilder {
@@ -41,10 +44,35 @@ public class NotificationEntryBuilder {
private final RankingBuilder mRankingBuilder = new RankingBuilder();
private StatusBarNotification mSbn = null;
+ /* ListEntry properties */
+ private GroupEntry mParent;
+ private int mSection;
+
public NotificationEntry build() {
StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
mRankingBuilder.setKey(sbn.getKey());
- return new NotificationEntry(sbn, mRankingBuilder.build());
+ final NotificationEntry entry = new NotificationEntry(sbn, mRankingBuilder.build());
+
+ /* ListEntry properties */
+ entry.setParent(mParent);
+ entry.setSection(mSection);
+ return entry;
+ }
+
+ /**
+ * Sets the parent.
+ */
+ public NotificationEntryBuilder setParent(@Nullable GroupEntry parent) {
+ mParent = parent;
+ return this;
+ }
+
+ /**
+ * Sets the section.
+ */
+ public NotificationEntryBuilder setSection(int section) {
+ mSection = section;
+ return this;
}
/**
@@ -176,7 +204,7 @@ public class NotificationEntryBuilder {
return this;
}
- public NotificationEntryBuilder setImportance(@Importance int importance) {
+ public NotificationEntryBuilder setImportance(@NotificationManager.Importance int importance) {
mRankingBuilder.setImportance(importance);
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 17d556dbd201..39ae68a40291 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -52,7 +52,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 1764bef57d27..10450fa82cde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -24,7 +24,6 @@ import android.service.notification.NotificationListenerService.RankingMap
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.NotificationEntryBuilder
import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationFilter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index a9413c78d770..ea6c70a6e142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -35,10 +35,10 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -85,7 +85,7 @@ public class DeviceProvisionedCoordinatorTest extends SysuiTestCase {
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
mDeviceProvisionedFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index ffaa335f91bc..01bca0dc1078 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -36,11 +36,11 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import org.junit.Before;
@@ -85,7 +85,7 @@ public class ForegroundCoordinatorTest extends SysuiTestCase {
ArgumentCaptor.forClass(NotifLifetimeExtender.class);
mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
lifetimeExtenderCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 527370e46b6e..979b8a906ef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -38,12 +38,12 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -86,14 +86,14 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mKeyguardCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture());
mKeyguardFilter = filterCaptor.getValue();
}
@Test
public void unfilteredState() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// THEN don't filter out the entry
assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
@@ -102,7 +102,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void notificationNotForCurrentProfile() {
// GIVEN the notification isn't for the given user
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(false);
// THEN filter out the entry
@@ -112,7 +112,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void keyguardNotShowing() {
// GIVEN the lockscreen isn't showing
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
when(mKeyguardStateController.isShowing()).thenReturn(false);
// THEN don't filter out the entry
@@ -122,7 +122,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void doNotShowLockscreenNotifications() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN we shouldn't show any lockscreen notifications
when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false);
@@ -134,7 +134,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void lockdown() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification's user is in lockdown:
when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true);
@@ -146,7 +146,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void publicMode_settingsDisallow() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification's user is in public mode and settings are configured to disallow
// notifications in public mode
@@ -161,7 +161,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void publicMode_notifDisallowed() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification's user is in public mode and settings are configured to disallow
// notifications in public mode
@@ -177,7 +177,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void doesNotExceedThresholdToShow() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification doesn't exceed the threshold to show on the lockscreen
mEntry.setRanking(new RankingBuilder()
@@ -191,34 +191,42 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
@Test
public void summaryExceedsThresholdToShow() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ // GIVEN the notification doesn't exceed the threshold to show on the lockscreen
+ // but it's part of a group (has a parent)
+ final GroupEntry parent = new GroupEntry("test_group_key");
+ final NotificationEntry entryWithParent = new NotificationEntryBuilder()
+ .setParent(parent)
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .build();
- // WHEN the notification doesn't exceed the threshold to show on the lockscreen
- // but its summary does
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
+ setupUnfilteredState(entryWithParent);
+ entryWithParent.setRanking(new RankingBuilder()
+ .setKey(entryWithParent.getKey())
.setImportance(IMPORTANCE_MIN)
.build());
- final NotificationEntry summary = new NotificationEntryBuilder().build();
- summary.setRanking(new RankingBuilder()
- .setKey(summary.getKey())
+ // WHEN its parent has a summary that exceeds threshold to show on lockscreen
+ parent.setSummary(new NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.build());
- final GroupEntry group = new GroupEntry(mEntry.getSbn().getGroupKey());
- group.setSummary(summary);
- mEntry.setParent(group);
// THEN don't filter out the entry
- assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
+ assertFalse(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
+
+ // WHEN its parent has a summary that doesn't exceed threshold to show on lockscreen
+ parent.setSummary(new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_MIN)
+ .build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
}
/**
* setup a state where the notification will not be filtered by the
* KeyguardNotificationCoordinator when the keyguard is showing.
*/
- private void setupUnfilteredState() {
+ private void setupUnfilteredState(NotificationEntry entry) {
// notification is for current profile
when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(true);
@@ -239,7 +247,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase {
// entry's ranking - should show on all lockscreens
// + priority of the notification exceeds the threshold to be shown on the lockscreen
- mEntry.setRanking(new RankingBuilder()
+ entry.setRanking(new RankingBuilder()
.setKey(mEntry.getKey())
.setVisibilityOverride(VISIBILITY_PUBLIC)
.setImportance(IMPORTANCE_HIGH)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 182e86667c63..d3b16c319692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -32,10 +32,10 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import org.junit.Before;
@@ -63,7 +63,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mRankingCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
mRankingFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
index 11488a0b699e..6fa1a89515c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
@@ -33,8 +33,8 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index d1398667f497..e23d0ae89a09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -39,12 +39,12 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 27e3a6699571..c7e59ef6bd3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -42,6 +42,7 @@ import android.view.ViewGroup;
import android.widget.RemoteViews;
import androidx.test.filters.SmallTest;
+import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.InflationTask;
@@ -64,7 +65,7 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
-@Ignore
+@Suppress
public class NotificationContentInflaterTest extends SysuiTestCase {
private NotificationContentInflater mNotificationInflater;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index bdca7efeb608..f513c2d1513b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -78,10 +78,10 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 9eba4ebeb994..f48c40c3b941 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -38,8 +38,8 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index d2b4a20ca3a9..deca51fe0033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -55,7 +55,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.EmptyShadeView;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -71,6 +70,7 @@ import com.android.systemui.statusbar.notification.NotificationSectionsFeatureMa
import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index dfd994139af8..7fa69018b496 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -33,9 +33,9 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index 3ad1e39f441e..54dc728e0c8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -26,8 +26,8 @@ import android.content.Context;
import android.os.UserHandle;
import com.android.systemui.R;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 575f145bcc63..f6ed4e6a2386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -40,7 +40,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -54,6 +53,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index d3fce567714d..d3ae7a783404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -98,7 +98,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -120,6 +119,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index c1f376bee525..53d8e5866347 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -42,9 +42,9 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions;
import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index b5e4cb965de8..f1a6e67edb43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,10 +51,10 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.ShadeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
new file mode 100644
index 000000000000..a39fbc4c232e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -0,0 +1,436 @@
+package com.android.systemui.util.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.ArrayMap
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.animation.PhysicsAnimator.EndListener
+import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PhysicsAnimatorTest : SysuiTestCase() {
+ private lateinit var viewGroup: ViewGroup
+ private lateinit var testView: View
+ private lateinit var testView2: View
+
+ private lateinit var animator: PhysicsAnimator<View>
+
+ private val springConfig = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ private val flingConfig = PhysicsAnimator.FlingConfig(2f)
+
+ private lateinit var mockUpdateListener: UpdateListener<View>
+ private lateinit var mockEndListener: EndListener<View>
+ private lateinit var mockEndAction: Runnable
+
+ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ mockUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+ mockEndListener = mock(EndListener::class.java) as EndListener<View>
+ mockEndAction = mock(Runnable::class.java)
+
+ viewGroup = FrameLayout(context)
+ testView = View(context)
+ testView2 = View(context)
+ viewGroup.addView(testView)
+ viewGroup.addView(testView2)
+
+ PhysicsAnimatorTestUtils.prepareForTest()
+
+ // Most of our tests involve checking the end state of animations, so we want calls that
+ // start animations to block the test thread until the animations have ended.
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+ animator = PhysicsAnimator.getInstance(testView)
+ }
+
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
+ @Test
+ fun testOneAnimatorPerView() {
+ assertEquals(animator, PhysicsAnimator.getInstance(testView))
+ assertEquals(PhysicsAnimator.getInstance(testView), PhysicsAnimator.getInstance(testView))
+ assertNotEquals(animator, PhysicsAnimator.getInstance(testView2))
+ }
+
+ @Test
+ fun testSpringOneProperty() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 50f, springConfig)
+ .start()
+
+ assertEquals(testView.translationX, 50f, 1f)
+ }
+
+ @Test
+ fun testSpringMultipleProperties() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+ .spring(DynamicAnimation.SCALE_Y, 1.1f, springConfig)
+ .start()
+
+ assertEquals(10f, testView.translationX, 1f)
+ assertEquals(50f, testView.translationY, 1f)
+ assertEquals(1.1f, testView.scaleY, 0.01f)
+ }
+
+ @Test
+ fun testFling() {
+ val startTime = System.currentTimeMillis()
+
+ animator
+ .fling(DynamicAnimation.TRANSLATION_X, 1000f /* startVelocity */, flingConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 500f, flingConfig)
+ .start()
+
+ val elapsedTimeSeconds = (System.currentTimeMillis() - startTime) / 1000f
+
+ // If the fling worked, the view should be somewhere between its starting position and the
+ // and the theoretical no-friction maximum of startVelocity (in pixels per second)
+ // multiplied by elapsedTimeSeconds. We can't calculate an exact expected location for a
+ // fling, so this is close enough.
+ assertTrue(testView.translationX > 0f)
+ assertTrue(testView.translationX < 1000f * elapsedTimeSeconds)
+ assertTrue(testView.translationY > 0f)
+ assertTrue(testView.translationY < 500f * elapsedTimeSeconds)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testEndListenersAndActions() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 500f, springConfig)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // Once TRANSLATION_X is done, the view should be at x = 10...
+ assertEquals(10f, testView.translationX, 1f)
+
+ // / ...TRANSLATION_Y should still be running...
+ assertTrue(animator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+
+ // ...and our end listener should have been called with x = 10, velocity = 0, and allEnded =
+ // false since TRANSLATION_Y is still running.
+ verify(mockEndListener).onAnimationEnd(
+ testView,
+ DynamicAnimation.TRANSLATION_X,
+ canceled = false,
+ finalValue = 10f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should not have been run yet.
+ verify(mockEndAction, times(0)).run()
+
+ // Block until TRANSLATION_Y finishes.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+ // The view should have been moved.
+ assertEquals(10f, testView.translationX, 1f)
+ assertEquals(500f, testView.translationY, 1f)
+
+ // The end listener should have been called, this time with TRANSLATION_Y, y = 50, and
+ // allEnded = true.
+ verify(mockEndListener).onAnimationEnd(
+ testView,
+ DynamicAnimation.TRANSLATION_Y,
+ canceled = false,
+ finalValue = 500f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // Now that all properties are done animating, the end action should have been called.
+ verify(mockEndAction, times(1)).run()
+ }
+
+ @Test
+ fun testUpdateListeners() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+ .addUpdateListener(object : UpdateListener<View> {
+ override fun onAnimationUpdateForProperty(
+ target: View,
+ values: UpdateMap<View>
+ ) {
+ mockUpdateListener.onAnimationUpdateForProperty(target, values)
+ }
+ })
+ .start()
+
+ verifyUpdateListenerCalls(animator, mockUpdateListener)
+ }
+
+ @Test
+ fun testListenersNotCalledOnSubsequentAnimations() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ verifyUpdateListenerCalls(animator, mockUpdateListener)
+ verify(mockEndListener, times(1)).onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
+ eq(true))
+ verify(mockEndAction, times(1)).run()
+
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 0f, springConfig)
+ .start()
+
+ // We didn't pass any of the listeners/actions to the subsequent animation, so they should
+ // never have been called.
+ verifyNoMoreInteractions(mockUpdateListener)
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testAnimationsUpdatedWhileInMotion() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Spring towards x = 100f.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 100f,
+ springConfig)
+ .start()
+
+ // Block until it reaches x = 50f.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { view -> view.translationX > 50f }
+
+ // Translation X value at the time of reversing the animation to spring to x = 0f.
+ val reversalTranslationX = testView.translationX
+
+ // Spring back towards 0f.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 0f,
+ // Lower the stiffness to ensure the update listener receives at least one
+ // update frame where the view has continued to move to the right.
+ springConfig.apply { stiffness = SpringForce.STIFFNESS_LOW })
+ .start()
+
+ // Wait for TRANSLATION_X.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // Verify that the animation continued past the X value at the time of reversal, before
+ // springing back. This ensures the change in direction was not abrupt.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > reversalTranslationX },
+ { u -> u.value < reversalTranslationX })
+
+ // Verify that the view is where it should be.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testAnimationsUpdatedWhileInMotion_originalListenersStillCalled() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Spring TRANSLATION_X to 100f, with an update and end listener provided.
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Wait until the animation is halfway there.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { view -> view.translationX > 50f }
+
+ // The end listener shouldn't have been called since the animation hasn't ended.
+ verifyNoMoreInteractions(mockEndListener)
+
+ // Make sure we called the update listener with appropriate values.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > 0f },
+ { u -> u.value >= 50f })
+
+ // Mock a second end listener.
+ val secondEndListener = mock(EndListener::class.java) as EndListener<View>
+ val secondUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+
+ // Start a new animation that springs both TRANSLATION_X and TRANSLATION_Y, and provide it
+ // the second end listener. This new end listener should be called for the end of
+ // TRANSLATION_X and TRANSLATION_Y, with allEnded = true when both have ended.
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 200f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 4000f, springConfig)
+ .addUpdateListener(secondUpdateListener)
+ .addEndListener(secondEndListener)
+ .start()
+
+ // Wait for TRANSLATION_X to end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // The update listener provided to the initial animation call (the one that only animated
+ // TRANSLATION_X) should have been called with values on the way to x = 200f. This is
+ // because the second animation call updated the original TRANSLATION_X animation.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > 100f }, { u -> u.value >= 200f })
+
+ // The original end listener should also have been called, with allEnded = true since it was
+ // provided to an animator that animated only TRANSLATION_X.
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The second end listener should have been called, but with allEnded = false since it was
+ // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
+ verify(secondEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+ verifyNoMoreInteractions(secondEndListener)
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+ // The original end listener shouldn't receive any callbacks because it was not provided to
+ // an animator that animated TRANSLATION_Y.
+ verifyNoMoreInteractions(mockEndListener)
+
+ verify(secondEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ verifyNoMoreInteractions(secondEndListener)
+ }
+
+ @Test
+ fun testFlingRespectsMinMax() {
+ animator
+ .fling(DynamicAnimation.TRANSLATION_X,
+ startVelocity = 1000f,
+ friction = 1.1f,
+ max = 10f)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Ensure that the view stopped at x = 10f, and the end listener was called once with that
+ // value.
+ assertEquals(10f, testView.translationX, 1f)
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
+ anyFloat(), eq(true))
+
+ animator
+ .fling(
+ DynamicAnimation.TRANSLATION_X,
+ startVelocity = -1000f,
+ friction = 1.1f,
+ min = -5f)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Ensure that the view stopped at x = -5f, and the end listener was called once with that
+ // value.
+ assertEquals(-5f, testView.translationX, 1f)
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
+ anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testExtensionProperty() {
+ testView
+ .physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 200f)
+ .start()
+
+ assertEquals(200f, testView.translationX, 1f)
+ }
+
+ /**
+ * Verifies that the calls to the mock update listener match the animation update frames
+ * reported by the test internal listener, in order.
+ */
+ private fun <T : Any> verifyUpdateListenerCalls(
+ animator: PhysicsAnimator<T>,
+ mockUpdateListener: UpdateListener<T>
+ ) {
+ val updates = getAnimationUpdateFrames(animator)
+
+ for (invocation in Mockito.mockingDetails(mockUpdateListener).invocations) {
+
+ // Grab the update map of Property -> AnimationUpdate that was passed to the mock update
+ // listener.
+ val updateMap = invocation.arguments[1]
+ as ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+ //
+ for ((property, update) in updateMap) {
+ val updatesForProperty = updates[property]!!
+
+ // This update should be the next one in the list for this property.
+ if (update != updatesForProperty[0]) {
+ Assert.fail("The update listener was called with an unexpected value: $update.")
+ }
+
+ updatesForProperty.remove(update)
+ }
+
+ // Mark this invocation verified.
+ verify(mockUpdateListener).onAnimationUpdateForProperty(animator.target, updateMap)
+ }
+
+ verifyNoMoreInteractions(mockUpdateListener)
+
+ // Since we were removing values as matching invocations were found, there should no longer
+ // be any values remaining. If there are, it means the update listener wasn't notified when
+ // it should have been.
+ assertEquals(0,
+ updates.values.fold(0, { count, propertyUpdates -> count + propertyUpdates.size }))
+
+ clearAnimationUpdateFrames(animator)
+ }
+} \ No newline at end of file
diff --git a/rs/java/android/renderscript/BaseObj.java b/rs/java/android/renderscript/BaseObj.java
index b7e05d9c984c..7b5514b8a0d1 100644
--- a/rs/java/android/renderscript/BaseObj.java
+++ b/rs/java/android/renderscript/BaseObj.java
@@ -16,8 +16,10 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import dalvik.system.CloseGuard;
+
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index b8eb3a1d7a40..0941907d35f8 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* <p>An Element represents one item within an {@link
diff --git a/rs/java/android/renderscript/FileA3D.java b/rs/java/android/renderscript/FileA3D.java
index 9a6b0bcd4544..7cc2825ae565 100644
--- a/rs/java/android/renderscript/FileA3D.java
+++ b/rs/java/android/renderscript/FileA3D.java
@@ -16,13 +16,13 @@
package android.renderscript;
-import java.io.File;
-import java.io.InputStream;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import java.io.File;
+import java.io.InputStream;
+
/**
* @hide
* @deprecated in API 16
diff --git a/rs/java/android/renderscript/Font.java b/rs/java/android/renderscript/Font.java
index 583350e91795..df9d8019f28d 100644
--- a/rs/java/android/renderscript/Font.java
+++ b/rs/java/android/renderscript/Font.java
@@ -16,17 +16,16 @@
package android.renderscript;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Environment;
+
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
-import android.os.Environment;
-
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-
/**
* @hide
* @deprecated in API 16
diff --git a/rs/java/android/renderscript/Matrix4f.java b/rs/java/android/renderscript/Matrix4f.java
index 026c9fbd7d5e..a9469c979494 100644
--- a/rs/java/android/renderscript/Matrix4f.java
+++ b/rs/java/android/renderscript/Matrix4f.java
@@ -16,8 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/Mesh.java b/rs/java/android/renderscript/Mesh.java
index 5321dcb957dc..826225a70d86 100644
--- a/rs/java/android/renderscript/Mesh.java
+++ b/rs/java/android/renderscript/Mesh.java
@@ -16,7 +16,8 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.Vector;
/**
diff --git a/rs/java/android/renderscript/Program.java b/rs/java/android/renderscript/Program.java
index e28d646f5f1c..ff072183e927 100644
--- a/rs/java/android/renderscript/Program.java
+++ b/rs/java/android/renderscript/Program.java
@@ -17,14 +17,14 @@
package android.renderscript;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.util.Log;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.util.Log;
-
/**
* @hide
diff --git a/rs/java/android/renderscript/ProgramFragment.java b/rs/java/android/renderscript/ProgramFragment.java
index 3dde9b6d6400..880531207b4d 100644
--- a/rs/java/android/renderscript/ProgramFragment.java
+++ b/rs/java/android/renderscript/ProgramFragment.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
index d05d41da8b6f..c741ce6e77ed 100644
--- a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramRaster.java b/rs/java/android/renderscript/ProgramRaster.java
index 33000acb4eb0..a21696c82161 100644
--- a/rs/java/android/renderscript/ProgramRaster.java
+++ b/rs/java/android/renderscript/ProgramRaster.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramStore.java b/rs/java/android/renderscript/ProgramStore.java
index 622fe21be47a..7e61347ee218 100644
--- a/rs/java/android/renderscript/ProgramStore.java
+++ b/rs/java/android/renderscript/ProgramStore.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramVertex.java b/rs/java/android/renderscript/ProgramVertex.java
index 83d9ea7be645..9257234de42c 100644
--- a/rs/java/android/renderscript/ProgramVertex.java
+++ b/rs/java/android/renderscript/ProgramVertex.java
@@ -38,7 +38,7 @@
**/
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramVertexFixedFunction.java b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
index 579d3bb507e8..03c2eaf91242 100644
--- a/rs/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/RSSurfaceView.java b/rs/java/android/renderscript/RSSurfaceView.java
index 561373cef625..6bdde387b334 100644
--- a/rs/java/android/renderscript/RSSurfaceView.java
+++ b/rs/java/android/renderscript/RSSurfaceView.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index f4c27771c846..46c49e5a5e11 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java
index 1797bef4be8d..862d032d6987 100644
--- a/rs/java/android/renderscript/RenderScriptCacheDir.java
+++ b/rs/java/android/renderscript/RenderScriptCacheDir.java
@@ -16,7 +16,8 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.File;
/**
diff --git a/rs/java/android/renderscript/RenderScriptGL.java b/rs/java/android/renderscript/RenderScriptGL.java
index 6fac83e8c4a8..dafaf367364d 100644
--- a/rs/java/android/renderscript/RenderScriptGL.java
+++ b/rs/java/android/renderscript/RenderScriptGL.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.view.Surface;
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index 9ad9aea9d7aa..d1d3a7642382 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.SparseArray;
/**
diff --git a/services/Android.bp b/services/Android.bp
index 8376d2bcc0f5..8be6e16c8e5c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -117,9 +117,6 @@ droidstubs {
" --hide ReferencesHidden" +
" --hide DeprecationMismatch" +
" --hide HiddenTypedefConstant",
- libs: [
- "framework-all",
- ],
visibility: ["//visibility:private"],
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
deleted file mode 100644
index 3dfe59e142a6..000000000000
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- ** Copyright 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 com.android.server.accessibility.gestures;
-
-import android.accessibilityservice.AccessibilityGestureEvent;
-import android.accessibilityservice.AccessibilityService;
-import android.content.Context;
-import android.gesture.GesturePoint;
-import android.graphics.PointF;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * This class handles gesture detection for the Touch Explorer. It collects
- * touch events and determines when they match a gesture, as well as when they
- * won't match a gesture. These state changes are then surfaced to mListener.
- */
-class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
-
- private static final boolean DEBUG = false;
-
- // Tag for logging received events.
- private static final String LOG_TAG = "AccessibilityGestureDetector";
-
- // Constants for sampling motion event points.
- // We sample based on a minimum distance between points, primarily to improve accuracy by
- // reducing noisy minor changes in direction.
- private static final float MIN_INCHES_BETWEEN_SAMPLES = 0.1f;
- private final float mMinPixelsBetweenSamplesX;
- private final float mMinPixelsBetweenSamplesY;
-
- // Constants for separating gesture segments
- private static final float ANGLE_THRESHOLD = 0.0f;
-
- // Constants for line segment directions
- private static final int LEFT = 0;
- private static final int RIGHT = 1;
- private static final int UP = 2;
- private static final int DOWN = 3;
- private static final int[][] DIRECTIONS_TO_GESTURE_ID = {
- {
- AccessibilityService.GESTURE_SWIPE_LEFT,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_RIGHT,
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_UP,
- AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
- AccessibilityService.GESTURE_SWIPE_DOWN
- }
- };
-
-
- /**
- * Listener functions are called as a result of onMoveEvent(). The current
- * MotionEvent in the context of these functions is the event passed into
- * onMotionEvent.
- */
- public interface Listener {
- /**
- * Called when the user has performed a double tap and then held down
- * the second tap.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- */
- void onDoubleTapAndHold(MotionEvent event, int policyFlags);
-
- /**
- * Called when the user lifts their finger on the second tap of a double
- * tap.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- *
- * @return true if the event is consumed, else false
- */
- boolean onDoubleTap(MotionEvent event, int policyFlags);
-
- /**
- * Called when the system has decided the event stream is a gesture.
- *
- * @return true if the event is consumed, else false
- */
- boolean onGestureStarted();
-
- /**
- * Called when an event stream is recognized as a gesture.
- *
- * @param gestureEvent Information about the gesture.
- *
- * @return true if the event is consumed, else false
- */
- boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
-
- /**
- * Called when the system has decided an event stream doesn't match any
- * known gesture.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- *
- * @return true if the event is consumed, else false
- */
- public boolean onGestureCancelled(MotionEvent event, int policyFlags);
- }
-
- private final Listener mListener;
- private final Context mContext; // Retained for on-demand construction of GestureDetector.
- private final GestureDetector mGestureDetector; // Double-tap detector.
-
- // Indicates that a single tap has occurred.
- private boolean mFirstTapDetected;
-
- // Indicates that the down event of a double tap has occured.
- private boolean mDoubleTapDetected;
-
- // Indicates that motion events are being collected to match a gesture.
- private boolean mRecognizingGesture;
-
- // Indicates that we've collected enough data to be sure it could be a
- // gesture.
- private boolean mGestureStarted;
-
- // Indicates that motion events from the second pointer are being checked
- // for a double tap.
- private boolean mSecondFingerDoubleTap;
-
- // Tracks the most recent time where ACTION_POINTER_DOWN was sent for the
- // second pointer.
- private long mSecondPointerDownTime;
-
- // Policy flags of the previous event.
- private int mPolicyFlags;
-
- // These values track the previous point that was saved to use for gesture
- // detection. They are only updated when the user moves more than the
- // recognition threshold.
- private float mPreviousGestureX;
- private float mPreviousGestureY;
-
- // These values track the previous point that was used to determine if there
- // was a transition into or out of gesture detection. They are updated when
- // the user moves more than the detection threshold.
- private float mBaseX;
- private float mBaseY;
- private long mBaseTime;
-
- // This is the calculated movement threshold used track if the user is still
- // moving their finger.
- private final float mGestureDetectionThreshold;
-
- // Buffer for storing points for gesture detection.
- private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
- // The minimal delta between moves to add a gesture point.
- private static final int TOUCH_TOLERANCE = 3;
-
- // The minimal score for accepting a predicted gesture.
- private static final float MIN_PREDICTION_SCORE = 2.0f;
-
- // Distance a finger must travel before we decide if it is a gesture or not.
- private static final int GESTURE_CONFIRM_MM = 10;
-
- // Time threshold used to determine if an interaction is a gesture or not.
- // If the first movement of 1cm takes longer than this value, we assume it's
- // a slow movement, and therefore not a gesture.
- //
- // This value was determined by measuring the time for the first 1cm
- // movement when gesturing, and touch exploring. Based on user testing,
- // all gestures started with the initial movement taking less than 100ms.
- // When touch exploring, the first movement almost always takes longer than
- // 200ms.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
-
- // Time threshold used to determine if a gesture should be cancelled. If
- // the finger takes more than this time to move 1cm, the ongoing gesture is
- // cancelled.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
-
- /**
- * Construct the gesture detector for {@link TouchExplorer}.
- *
- * @see #AccessibilityGestureDetector(Context, Listener, GestureDetector)
- */
- AccessibilityGestureDetector(Context context, Listener listener) {
- this(context, listener, null);
- }
-
- /**
- * Construct the gesture detector for {@link TouchExplorer}.
- *
- * @param context A context handle for accessing resources.
- * @param listener A listener to callback with gesture state or information.
- * @param detector The gesture detector to handle touch event. If null the default one created
- * in place, or for testing purpose.
- */
- AccessibilityGestureDetector(Context context, Listener listener, GestureDetector detector) {
- mListener = listener;
- mContext = context;
-
- // Break the circular dependency between constructors and let the class to be testable
- if (detector == null) {
- mGestureDetector = new GestureDetector(context, this);
- } else {
- mGestureDetector = detector;
- }
- mGestureDetector.setOnDoubleTapListener(this);
- mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
- context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
-
- // Calculate minimum gesture velocity
- final float pixelsPerInchX = context.getResources().getDisplayMetrics().xdpi;
- final float pixelsPerInchY = context.getResources().getDisplayMetrics().ydpi;
- mMinPixelsBetweenSamplesX = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchX;
- mMinPixelsBetweenSamplesY = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchY;
- }
-
- /**
- * Handle a motion event. If an action is completed, the appropriate
- * callback on mListener is called, and the return value of the callback is
- * passed to the caller.
- *
- * @param event The transformed motion event to be handled.
- * @param rawEvent The raw motion event. It's important that this be the raw
- * event, before any transformations have been applied, so that measurements
- * can be made in physical units.
- * @param policyFlags Policy flags for the event.
- *
- * @return true if the event is consumed, else false
- */
- public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- // The accessibility gesture detector is interested in the movements in physical space,
- // so it uses the rawEvent to ignore magnification and other transformations.
- final float x = rawEvent.getX();
- final float y = rawEvent.getY();
- final long time = rawEvent.getEventTime();
-
- mPolicyFlags = policyFlags;
- switch (rawEvent.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDoubleTapDetected = false;
- mSecondFingerDoubleTap = false;
- mRecognizingGesture = true;
- mGestureStarted = false;
- mPreviousGestureX = x;
- mPreviousGestureY = y;
- mStrokeBuffer.clear();
- mStrokeBuffer.add(new GesturePoint(x, y, time));
-
- mBaseX = x;
- mBaseY = y;
- mBaseTime = time;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mRecognizingGesture) {
- final float deltaX = mBaseX - x;
- final float deltaY = mBaseY - y;
- final double moveDelta = Math.hypot(deltaX, deltaY);
- if (moveDelta > mGestureDetectionThreshold) {
- // If the pointer has moved more than the threshold,
- // update the stored values.
- mBaseX = x;
- mBaseY = y;
- mBaseTime = time;
-
- // Since the pointer has moved, this is not a double
- // tap.
- mFirstTapDetected = false;
- mDoubleTapDetected = false;
-
- // If this hasn't been confirmed as a gesture yet, send
- // the event.
- if (!mGestureStarted) {
- mGestureStarted = true;
- return mListener.onGestureStarted();
- }
- } else if (!mFirstTapDetected) {
- // The finger may not move if they are double tapping.
- // In that case, we shouldn't cancel the gesture.
- final long timeDelta = time - mBaseTime;
- final long threshold = mGestureStarted ?
- CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
- CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
-
- // If the pointer hasn't moved for longer than the
- // timeout, cancel gesture detection.
- if (timeDelta > threshold) {
- cancelGesture();
- return mListener.onGestureCancelled(rawEvent, policyFlags);
- }
- }
-
- final float dX = Math.abs(x - mPreviousGestureX);
- final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
- mPreviousGestureX = x;
- mPreviousGestureY = y;
- mStrokeBuffer.add(new GesturePoint(x, y, time));
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- if (mDoubleTapDetected) {
- return finishDoubleTap(rawEvent, policyFlags);
- }
- if (mGestureStarted) {
- final float dX = Math.abs(x - mPreviousGestureX);
- final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
- mStrokeBuffer.add(new GesturePoint(x, y, time));
- }
- return recognizeGesture(rawEvent, policyFlags);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- // Once a second finger is used, we're definitely not
- // recognizing a gesture.
- cancelGesture();
-
- if (rawEvent.getPointerCount() == 2) {
- // If this was the second finger, attempt to recognize double
- // taps on it.
- mSecondFingerDoubleTap = true;
- mSecondPointerDownTime = time;
- } else {
- // If there are more than two fingers down, stop watching
- // for a double tap.
- mSecondFingerDoubleTap = false;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- // If we're detecting taps on the second finger, see if we
- // should finish the double tap.
- if (mSecondFingerDoubleTap && mDoubleTapDetected) {
- return finishDoubleTap(rawEvent, policyFlags);
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- clear();
- break;
- }
-
- // If we're detecting taps on the second finger, map events from the
- // finger to the first finger.
- if (mSecondFingerDoubleTap) {
- MotionEvent newEvent = mapSecondPointerToFirstPointer(rawEvent);
- if (newEvent == null) {
- return false;
- }
- boolean handled = mGestureDetector.onTouchEvent(newEvent);
- newEvent.recycle();
- return handled;
- }
-
- if (!mRecognizingGesture) {
- return false;
- }
-
- // Pass the transformed event on to the standard gesture detector.
- return mGestureDetector.onTouchEvent(event);
- }
-
- public void clear() {
- mFirstTapDetected = false;
- mDoubleTapDetected = false;
- mSecondFingerDoubleTap = false;
- mGestureStarted = false;
- mGestureDetector.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0));
- cancelGesture();
- }
-
-
- @Override
- public void onLongPress(MotionEvent e) {
- maybeSendLongPress(e, mPolicyFlags);
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent event) {
- mFirstTapDetected = true;
- return false;
- }
-
- @Override
- public boolean onSingleTapConfirmed(MotionEvent event) {
- clear();
- return false;
- }
-
- @Override
- public boolean onDoubleTap(MotionEvent event) {
- // The processing of the double tap is deferred until the finger is
- // lifted, so that we can detect a long press on the second tap.
- mDoubleTapDetected = true;
- return false;
- }
-
- private void maybeSendLongPress(MotionEvent event, int policyFlags) {
- if (!mDoubleTapDetected) {
- return;
- }
-
- clear();
-
- mListener.onDoubleTapAndHold(event, policyFlags);
- }
-
- private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
- clear();
-
- return mListener.onDoubleTap(event, policyFlags);
- }
-
- private void cancelGesture() {
- mRecognizingGesture = false;
- mGestureStarted = false;
- mStrokeBuffer.clear();
- }
-
- /**
- * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
- * Listener callbacks for success or failure.
- *
- * @param event The raw motion event to pass to the listener callbacks.
- * @param policyFlags Policy flags for the event.
- *
- * @return true if the event is consumed, else false
- */
- private boolean recognizeGesture(MotionEvent event, int policyFlags) {
- if (mStrokeBuffer.size() < 2) {
- return mListener.onGestureCancelled(event, policyFlags);
- }
-
- // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
- // direction change.
- // Method: for each sampled motion event, check the angle of the most recent motion vector
- // versus the preceding motion vector, and segment the line if the angle is about
- // 90 degrees.
-
- ArrayList<PointF> path = new ArrayList<>();
- PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
- path.add(lastDelimiter);
-
- float dX = 0; // Sum of unit vectors from last delimiter to each following point
- float dY = 0;
- int count = 0; // Number of points since last delimiter
- float length = 0; // Vector length from delimiter to most recent point
-
- PointF next = new PointF();
- for (int i = 1; i < mStrokeBuffer.size(); ++i) {
- next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
- if (count > 0) {
- // Average of unit vectors from delimiter to following points
- float currentDX = dX / count;
- float currentDY = dY / count;
-
- // newDelimiter is a possible new delimiter, based on a vector with length from
- // the last delimiter to the previous point, but in the direction of the average
- // unit vector from delimiter to previous points.
- // Using the averaged vector has the effect of "squaring off the curve",
- // creating a sharper angle between the last motion and the preceding motion from
- // the delimiter. In turn, this sharper angle achieves the splitting threshold
- // even in a gentle curve.
- PointF newDelimiter = new PointF(length * currentDX + lastDelimiter.x,
- length * currentDY + lastDelimiter.y);
-
- // Unit vector from newDelimiter to the most recent point
- float nextDX = next.x - newDelimiter.x;
- float nextDY = next.y - newDelimiter.y;
- float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
- nextDX = nextDX / nextLength;
- nextDY = nextDY / nextLength;
-
- // Compare the initial motion direction to the most recent motion direction,
- // and segment the line if direction has changed by about 90 degrees.
- float dot = currentDX * nextDX + currentDY * nextDY;
- if (dot < ANGLE_THRESHOLD) {
- path.add(newDelimiter);
- lastDelimiter = newDelimiter;
- dX = 0;
- dY = 0;
- count = 0;
- }
- }
-
- // Vector from last delimiter to most recent point
- float currentDX = next.x - lastDelimiter.x;
- float currentDY = next.y - lastDelimiter.y;
- length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
-
- // Increment sum of unit vectors from delimiter to each following point
- count = count + 1;
- dX = dX + currentDX / length;
- dY = dY + currentDY / length;
- }
-
- path.add(next);
- Slog.i(LOG_TAG, "path=" + path.toString());
-
- // Classify line segments, and call Listener callbacks.
- return recognizeGesturePath(event, policyFlags, path);
- }
-
- /**
- * Classifies a pair of line segments, by direction.
- * Calls Listener callbacks for success or failure.
- *
- * @param event The raw motion event to pass to the listener's onGestureCanceled method.
- * @param policyFlags Policy flags for the event.
- * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
- *
- * @return true if the event is consumed, else false
- */
- private boolean recognizeGesturePath(MotionEvent event, int policyFlags,
- ArrayList<PointF> path) {
-
- final int displayId = event.getDisplayId();
- if (path.size() == 2) {
- PointF start = path.get(0);
- PointF end = path.get(1);
-
- float dX = end.x - start.x;
- float dY = end.y - start.y;
- int direction = toDirection(dX, dY);
- switch (direction) {
- case LEFT:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
- displayId));
- case RIGHT:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
- displayId));
- case UP:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
- displayId));
- case DOWN:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
- displayId));
- default:
- // Do nothing.
- }
-
- } else if (path.size() == 3) {
- PointF start = path.get(0);
- PointF mid = path.get(1);
- PointF end = path.get(2);
-
- float dX0 = mid.x - start.x;
- float dY0 = mid.y - start.y;
-
- float dX1 = end.x - mid.x;
- float dY1 = end.y - mid.y;
-
- int segmentDirection0 = toDirection(dX0, dY0);
- int segmentDirection1 = toDirection(dX1, dY1);
- int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(gestureId, displayId));
- }
- // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
- return mListener.onGestureCancelled(event, policyFlags);
- }
-
- /** Maps a vector to a dominant direction in set {LEFT, RIGHT, UP, DOWN}. */
- private static int toDirection(float dX, float dY) {
- if (Math.abs(dX) > Math.abs(dY)) {
- // Horizontal
- return (dX < 0) ? LEFT : RIGHT;
- } else {
- // Vertical
- return (dY < 0) ? UP : DOWN;
- }
- }
-
- private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
- // Only map basic events when two fingers are down.
- if (event.getPointerCount() != 2 ||
- (event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN &&
- event.getActionMasked() != MotionEvent.ACTION_POINTER_UP &&
- event.getActionMasked() != MotionEvent.ACTION_MOVE)) {
- return null;
- }
-
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_POINTER_UP) {
- action = MotionEvent.ACTION_UP;
- }
-
- // Map the information from the second pointer to the first.
- return MotionEvent.obtain(mSecondPointerDownTime, event.getEventTime(), action,
- event.getX(1), event.getY(1), event.getPressure(1), event.getSize(1),
- event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
- event.getDeviceId(), event.getEdgeFlags());
- }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
new file mode 100644
index 000000000000..9b7adc883dee
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -0,0 +1,232 @@
+/*
+ * 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.server.accessibility.gestures;
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import static com.android.server.accessibility.gestures.Swipe.DOWN;
+import static com.android.server.accessibility.gestures.Swipe.LEFT;
+import static com.android.server.accessibility.gestures.Swipe.RIGHT;
+import static com.android.server.accessibility.gestures.Swipe.UP;
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class coordinates a series of individual gesture matchers to serve as a unified gesture
+ * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
+ * when a gesture starts or completes.
+ */
+class GestureManifold implements GestureMatcher.StateChangeListener {
+
+ private static final String LOG_TAG = "GestureManifold";
+
+ private final List<GestureMatcher> mGestures = new ArrayList<>();
+ private final Context mContext;
+ // Handler for performing asynchronous operations.
+ private final Handler mHandler;
+ // Listener to be notified of gesture start and end.
+ private Listener mListener;
+ // Shared state information.
+ private TouchState mState;
+
+ GestureManifold(Context context, Listener listener, TouchState state) {
+ mContext = context;
+ mHandler = new Handler(context.getMainLooper());
+ mListener = listener;
+ mState = state;
+ // Set up gestures.
+ // Start with double tap.
+ mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
+ mGestures.add(new MultiTapAndHold(context, 2, GESTURE_DOUBLE_TAP_AND_HOLD, this));
+ // One-direction swipes.
+ mGestures.add(new Swipe(context, RIGHT, GESTURE_SWIPE_RIGHT, this));
+ mGestures.add(new Swipe(context, LEFT, GESTURE_SWIPE_LEFT, this));
+ mGestures.add(new Swipe(context, UP, GESTURE_SWIPE_UP, this));
+ mGestures.add(new Swipe(context, DOWN, GESTURE_SWIPE_DOWN, this));
+ // Two-direction swipes.
+ mGestures.add(new Swipe(context, LEFT, RIGHT, GESTURE_SWIPE_LEFT_AND_RIGHT, this));
+ mGestures.add(new Swipe(context, LEFT, UP, GESTURE_SWIPE_LEFT_AND_UP, this));
+ mGestures.add(new Swipe(context, LEFT, DOWN, GESTURE_SWIPE_LEFT_AND_DOWN, this));
+ mGestures.add(new Swipe(context, RIGHT, UP, GESTURE_SWIPE_RIGHT_AND_UP, this));
+ mGestures.add(new Swipe(context, RIGHT, DOWN, GESTURE_SWIPE_RIGHT_AND_DOWN, this));
+ mGestures.add(new Swipe(context, RIGHT, LEFT, GESTURE_SWIPE_RIGHT_AND_LEFT, this));
+ mGestures.add(new Swipe(context, DOWN, UP, GESTURE_SWIPE_DOWN_AND_UP, this));
+ mGestures.add(new Swipe(context, DOWN, LEFT, GESTURE_SWIPE_DOWN_AND_LEFT, this));
+ mGestures.add(new Swipe(context, DOWN, RIGHT, GESTURE_SWIPE_DOWN_AND_RIGHT, this));
+ mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this));
+ mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this));
+ mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this));
+ }
+
+ /**
+ * Processes a motion event.
+ *
+ * @param event The event as received from the previous entry in the event stream.
+ * @param rawEvent The event without any transformations e.g. magnification.
+ * @param policyFlags
+ * @return True if the event has been appropriately handled by the gesture manifold and related
+ * callback functions, false if it should be handled further by the calling function.
+ */
+ boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mState.isClear()) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // Sanity safeguard: if touch state is clear, then matchers should always be clear
+ // before processing the next down event.
+ clear();
+ } else {
+ // If for some reason other events come through while in the clear state they could
+ // compromise the state of particular matchers, so we just ignore them.
+ return false;
+ }
+ }
+ for (GestureMatcher matcher : mGestures) {
+ if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, matcher.toString());
+ }
+ matcher.onMotionEvent(event, rawEvent, policyFlags);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, matcher.toString());
+ }
+ if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
+ // Here we just clear and return. The actual gesture dispatch is done in
+ // onStateChanged().
+ clear();
+ // No need to process this event any further.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void clear() {
+ for (GestureMatcher matcher : mGestures) {
+ matcher.clear();
+ }
+ }
+
+ /**
+ * Listener that receives notifications of the state of the gesture detector. Listener functions
+ * are called as a result of onMotionEvent(). The current MotionEvent in the context of these
+ * functions is the event passed into onMotionEvent.
+ */
+ public interface Listener {
+ /**
+ * Called when the user has performed a double tap and then held down the second tap.
+ */
+ void onDoubleTapAndHold();
+
+ /**
+ * Called when the user lifts their finger on the second tap of a double tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap();
+
+ /**
+ * Called when the system has decided the event stream is a gesture.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureStarted();
+
+ /**
+ * Called when an event stream is recognized as a gesture.
+ *
+ * @param gestureEvent Information about the gesture.
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
+
+ /**
+ * Called when the system has decided an event stream doesn't match any known gesture.
+ *
+ * @param event The most recent MotionEvent received.
+ * @param policyFlags The policy flags of the most recent event.
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ }
+
+ @Override
+ public void onStateChanged(
+ int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (state == GestureMatcher.STATE_GESTURE_STARTED && !mState.isGestureDetecting()) {
+ mListener.onGestureStarted();
+ } else if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
+ onGestureCompleted(gestureId);
+ } else if (state == GestureMatcher.STATE_GESTURE_CANCELED && mState.isGestureDetecting()) {
+ // We only want to call the cancelation callback if there are no other pending
+ // detectors.
+ for (GestureMatcher matcher : mGestures) {
+ if (matcher.getState() == GestureMatcher.STATE_GESTURE_STARTED) {
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Cancelling.");
+ }
+ mListener.onGestureCancelled(event, rawEvent, policyFlags);
+ }
+ }
+
+ private void onGestureCompleted(int gestureId) {
+ MotionEvent event = mState.getLastReceivedEvent();
+ // Note that gestures that complete immediately call clear() from onMotionEvent.
+ // Gestures that complete on a delay call clear() here.
+ switch (gestureId) {
+ case GESTURE_DOUBLE_TAP:
+ mListener.onDoubleTap();
+ clear();
+ break;
+ case GESTURE_DOUBLE_TAP_AND_HOLD:
+ mListener.onDoubleTapAndHold();
+ clear();
+ break;
+ default:
+ AccessibilityGestureEvent gestureEvent =
+ new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+ mListener.onGestureCompleted(gestureEvent);
+ break;
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
new file mode 100644
index 000000000000..0b30ff57ddde
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
@@ -0,0 +1,371 @@
+/*
+ * 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.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class describes a common base for gesture matchers. A gesture matcher checks a series of
+ * motion events against a single gesture. Coordinating the individual gesture matchers is done by
+ * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove,
+ * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in
+ * response to that type of event. Finally, be sure to give your gesture a name by overriding
+ * getGestureName().
+ */
+abstract class GestureMatcher {
+ // Potential states for this individual gesture matcher.
+ // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled
+ // that there is enough data to judge that a gesture has started.
+ static final int STATE_CLEAR = 0;
+ // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled
+ // to the gesture manifold that what looks like the specified gesture has started.
+ static final int STATE_GESTURE_STARTED = 1;
+ // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and
+ // will not accept motion events until it is cleared.
+ static final int STATE_GESTURE_COMPLETED = 2;
+ // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is
+ // impossible that this set of motion events will match the specified gesture.
+ static final int STATE_GESTURE_CANCELED = 3;
+
+ @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED})
+ public @interface State {}
+
+ @State private int mState = STATE_CLEAR;
+ // The id number of the gesture that gets passed to accessibility services.
+ private final int mGestureId;
+ // handler for asynchronous operations like timeouts
+ private final Handler mHandler;
+
+ private final StateChangeListener mListener;
+
+ // Use this to transition to new states after a delay.
+ // e.g. cancel or complete after some timeout.
+ // Convenience functions for tapTimeout and doubleTapTimeout are already defined here.
+ protected final DelayedTransition mDelayedTransition;
+
+ GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) {
+ mGestureId = gestureId;
+ mHandler = handler;
+ mDelayedTransition = new DelayedTransition();
+ mListener = listener;
+ }
+
+ /**
+ * Resets all state information for this matcher. Subclasses that include their own state
+ * information should override this method to reset their own state information and call
+ * super.clear().
+ */
+ protected void clear() {
+ mState = STATE_CLEAR;
+ cancelPendingTransitions();
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Transitions to a new state and notifies any listeners. Note that any pending transitions are
+ * canceled.
+ */
+ private void setState(
+ @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mState = state;
+ cancelPendingTransitions();
+ mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates that there is evidence to suggest that this gesture has started. */
+ protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates this stream of motion events can no longer match this gesture. */
+ protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates this gesture is completed. */
+ protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags);
+ }
+
+ public int getGestureId() {
+ return mGestureId;
+ }
+
+ /**
+ * Process a motion event and attempt to match it to this gesture.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ * @return the state of this matcher.
+ */
+ public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) {
+ return mState;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ onDown(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ onPointerDown(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onMove(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onPointerUp(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_UP:
+ onUp(event, rawEvent, policyFlags);
+ break;
+ default:
+ // Cancel because of invalid event.
+ setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+ break;
+ }
+ return mState;
+ }
+
+ /**
+ * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate
+ * the first finger has touched the screen. If not overridden the default response is to do
+ * nothing.
+ */
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN
+ * indicates that more than one finger has touched the screen. If not overridden the default
+ * response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that
+ * one or fingers has moved. If not overridden the default response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP
+ * indicates that a finger has lifted from the screen but at least one finger continues to touch
+ * the screen. If not overridden the default response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there
+ * are no more fingers touching the screen. If not overridden the default response is to do
+ * nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */
+ protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */
+ protected final void cancelAfterDoubleTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used
+ * to prevent this matcher from accepting motion events until it is cleared.
+ */
+ protected final void cancelAfter(
+ long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mDelayedTransition.cancel();
+ mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags);
+ }
+
+ /** Cancels any delayed transitions between states scheduled for this matcher. */
+ protected final void cancelPendingTransitions() {
+ mDelayedTransition.cancel();
+ }
+
+ /**
+ * Signals that this gesture has been completed after the tap timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfterLongPressTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the tap timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfterTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the specified timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfter(
+ long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mDelayedTransition.cancel();
+ mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the double-tap timeout has expired. Used
+ * to ensure that there is no conflict with another gesture or for gestures that explicitly
+ * require a hold.
+ */
+ protected final void completeAfterDoubleTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ public static String getStateSymbolicName(@State int state) {
+ switch (state) {
+ case STATE_CLEAR:
+ return "STATE_CLEAR";
+ case STATE_GESTURE_STARTED:
+ return "STATE_GESTURE_STARTED";
+ case STATE_GESTURE_COMPLETED:
+ return "STATE_GESTURE_COMPLETED";
+ case STATE_GESTURE_CANCELED:
+ return "STATE_GESTURE_CANCELED";
+ default:
+ return "Unknown state: " + state;
+ }
+ }
+
+ /**
+ * Returns a readable name for this matcher that can be displayed to the user and in system
+ * logs.
+ */
+ abstract String getGestureName();
+
+ /**
+ * Returns a String representation of this matcher. Each matcher can override this method to add
+ * extra state information to the string representation.
+ */
+ public String toString() {
+ return getGestureName() + ":" + getStateSymbolicName(mState);
+ }
+
+ /** This class allows matchers to transition between states on a delay. */
+ protected final class DelayedTransition implements Runnable {
+
+ private static final String LOG_TAG = "GestureMatcher.DelayedTransition";
+ int mTargetState;
+ MotionEvent mEvent;
+ MotionEvent mRawEvent;
+ int mPolicyFlags;
+
+ public void cancel() {
+ // Avoid meaningless debug messages.
+ if (DEBUG && isPending()) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": canceling delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ mHandler.removeCallbacks(this);
+ }
+
+ public void post(
+ int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mTargetState = state;
+ mEvent = event;
+ mRawEvent = rawEvent;
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ if (DEBUG) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": posting delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ }
+
+ public boolean isPending() {
+ return mHandler.hasCallbacks(this);
+ }
+
+ public void forceSendAndRemove() {
+ if (isPending()) {
+ run();
+ cancel();
+ }
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": executing delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ setState(mTargetState, mEvent, mRawEvent, mPolicyFlags);
+ }
+ }
+
+ /** Interface to allow a class to listen for state changes in a specific gesture matcher */
+ interface StateChangeListener {
+
+ void onStateChanged(
+ int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
new file mode 100644
index 000000000000..2891c6c294f5
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -0,0 +1,145 @@
+/*
+ * 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.server.accessibility.gestures;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class matches multi-tap gestures. The number of taps for each instance is specified in the
+ * constructor.
+ */
+class MultiTap extends GestureMatcher {
+
+ // Maximum reasonable number of taps.
+ public static final int MAX_TAPS = 10;
+ final int mTargetTaps;
+ // The acceptable distance between two taps
+ int mDoubleTapSlop;
+ // The acceptable distance the pointer can move and still count as a tap.
+ int mTouchSlop;
+ int mTapTimeout;
+ int mDoubleTapTimeout;
+ int mCurrentTaps;
+ float mBaseX;
+ float mBaseY;
+
+ MultiTap(Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mTargetTaps = taps;
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mCurrentTaps = 0;
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ super.clear();
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterTapTimeout(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+ if (!isInsideSlop(rawEvent, mDoubleTapSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ if (!isInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
+ mCurrentTaps++;
+ if (mCurrentTaps == mTargetTaps) {
+ // Done.
+ completeAfterTapTimeout(event, rawEvent, policyFlags);
+ return;
+ }
+ // Needs more taps.
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ } else {
+ // Either too many taps or nonsensical event stream.
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (!isInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Double Tap";
+ case 3:
+ return "Triple Tap";
+ default:
+ return Integer.toString(mTargetTaps) + " Taps";
+ }
+ }
+
+ private boolean isInsideSlop(MotionEvent rawEvent, int slop) {
+ final float deltaX = mBaseX - rawEvent.getX();
+ final float deltaY = mBaseY - rawEvent.getY();
+ if (deltaX == 0 && deltaY == 0) {
+ return true;
+ }
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ return moveDelta <= slop;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString()
+ + ", Taps:"
+ + mCurrentTaps
+ + ", mBaseX: "
+ + Float.toString(mBaseX)
+ + ", mBaseY: "
+ + Float.toString(mBaseY);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
new file mode 100644
index 000000000000..6a1f1a546bc2
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
@@ -0,0 +1,51 @@
+/*
+ * 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.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-tap and hold. The number of taps for each instance
+ * is specified in the constructor.
+ */
+class MultiTapAndHold extends MultiTap {
+ MultiTapAndHold(
+ Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(context, taps, gesture, listener);
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ super.onDown(event, rawEvent, policyFlags);
+ if (mCurrentTaps + 1 == mTargetTaps) {
+ completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Double Tap and Hold";
+ case 3:
+ return "Triple Tap and Hold";
+ default:
+ return Integer.toString(mTargetTaps) + " Taps and Hold";
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
new file mode 100644
index 000000000000..b246c67944c7
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
@@ -0,0 +1,430 @@
+/*
+ * 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.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.content.Context;
+import android.gesture.GesturePoint;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe
+ * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc.
+ * At this time swipes with more than two directions are not supported.
+ */
+class Swipe extends GestureMatcher {
+
+ // Direction constants.
+ public static final int LEFT = 0;
+ public static final int RIGHT = 1;
+ public static final int UP = 2;
+ public static final int DOWN = 3;
+ // This is the calculated movement threshold used track if the user is still
+ // moving their finger.
+ private final float mGestureDetectionThreshold;
+
+ // Buffer for storing points for gesture detection.
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // The minimal delta between moves to add a gesture point.
+ private static final int TOUCH_TOLERANCE_PIX = 3;
+
+ // The minimal score for accepting a predicted gesture.
+ private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+ // Distance a finger must travel before we decide if it is a gesture or not.
+ private static final int GESTURE_CONFIRM_CM = 1;
+
+ // Time threshold used to determine if an interaction is a gesture or not.
+ // If the first movement of 1cm takes longer than this value, we assume it's
+ // a slow movement, and therefore not a gesture.
+ //
+ // This value was determined by measuring the time for the first 1cm
+ // movement when gesturing, and touch exploring. Based on user testing,
+ // all gestures started with the initial movement taking less than 100ms.
+ // When touch exploring, the first movement almost always takes longer than
+ // 200ms.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
+
+ // Time threshold used to determine if a gesture should be cancelled. If
+ // the finger takes more than this time to move 1cm, the ongoing gesture is
+ // cancelled.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
+
+ private int[] mDirections;
+ private float mBaseX;
+ private float mBaseY;
+ private long mBaseTime;
+ private float mPreviousGestureX;
+ private float mPreviousGestureY;
+ // Constants for sampling motion event points.
+ // We sample based on a minimum distance between points, primarily to improve accuracy by
+ // reducing noisy minor changes in direction.
+ private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f;
+ private final float mMinPixelsBetweenSamplesX;
+ private final float mMinPixelsBetweenSamplesY;
+
+ // Constants for separating gesture segments
+ private static final float ANGLE_THRESHOLD = 0.0f;
+
+ Swipe(
+ Context context,
+ int direction,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ this(context, new int[] {direction}, gesture, listener);
+ }
+
+ Swipe(
+ Context context,
+ int direction1,
+ int direction2,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ this(context, new int[] {direction1, direction2}, gesture, listener);
+ }
+
+ private Swipe(
+ Context context,
+ int[] directions,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mDirections = directions;
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ mGestureDetectionThreshold =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics)
+ * GESTURE_CONFIRM_CM;
+ // Calculate minimum gesture velocity
+ final float pixelsPerCmX = displayMetrics.xdpi / 2.54f;
+ final float pixelsPerCmY = displayMetrics.ydpi / 2.54f;
+ mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX;
+ mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY;
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ mBaseTime = 0;
+ mStrokeBuffer.clear();
+ super.clear();
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = rawEvent.getX();
+ mBaseY = rawEvent.getY();
+ mBaseTime = event.getEventTime();
+ mPreviousGestureX = mBaseX;
+ mPreviousGestureY = mBaseY;
+ }
+ // Otherwise do nothing because this event doesn't make sense in the middle of a gesture.
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ final float x = rawEvent.getX();
+ final float y = rawEvent.getY();
+ final long time = event.getEventTime();
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ final long timeDelta = time - mBaseTime;
+ final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY));
+ if (DEBUG) {
+ Slog.d(
+ getGestureName(),
+ "moveDelta:"
+ + Double.toString(moveDelta)
+ + " mGestureDetectionThreshold: "
+ + Float.toString(mGestureDetectionThreshold));
+ }
+ if (getState() == STATE_CLEAR) {
+ if (mStrokeBuffer.size() == 0) {
+ // First, make sure the pointer is going in the right direction.
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ int direction = toDirection(x - mBaseX, y - mBaseY);
+ if (direction != mDirections[0]) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ } else {
+ // This is confirmed to be some kind of swipe so start tracking points.
+ mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime));
+ }
+ }
+ if (moveDelta > mGestureDetectionThreshold) {
+ // If the pointer has moved more than the threshold,
+ // update the stored values.
+ mBaseX = x;
+ mBaseY = y;
+ mBaseTime = time;
+ if (getState() == STATE_CLEAR) {
+ startGesture(event, rawEvent, policyFlags);
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ }
+ }
+ }
+ if (getState() == STATE_GESTURE_STARTED) {
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mPreviousGestureX = x;
+ mPreviousGestureY = y;
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ }
+ }
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (getState() != STATE_GESTURE_STARTED) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+
+ final float x = rawEvent.getX();
+ final float y = rawEvent.getY();
+ final long time = event.getEventTime();
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ }
+ recognizeGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ /**
+ * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
+ * transitioned to STATE_GESTURE_STARTED the delay is longer.
+ */
+ private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelPendingTransitions();
+ switch (getState()) {
+ case STATE_CLEAR:
+ cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags);
+ break;
+ case STATE_GESTURE_STARTED:
+ cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
+ * Listener callbacks for success or failure.
+ *
+ * @param event The raw motion event to pass to the listener callbacks.
+ * @param policyFlags Policy flags for the event.
+ * @return true if the event is consumed, else false
+ */
+ private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mStrokeBuffer.size() < 2) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+
+ // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
+ // direction change.
+ // Method: for each sampled motion event, check the angle of the most recent motion vector
+ // versus the preceding motion vector, and segment the line if the angle is about
+ // 90 degrees.
+
+ ArrayList<PointF> path = new ArrayList<>();
+ PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+ path.add(lastDelimiter);
+
+ float dX = 0; // Sum of unit vectors from last delimiter to each following point
+ float dY = 0;
+ int count = 0; // Number of points since last delimiter
+ float length = 0; // Vector length from delimiter to most recent point
+
+ PointF next = new PointF();
+ for (int i = 1; i < mStrokeBuffer.size(); ++i) {
+ next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+ if (count > 0) {
+ // Average of unit vectors from delimiter to following points
+ float currentDX = dX / count;
+ float currentDY = dY / count;
+
+ // newDelimiter is a possible new delimiter, based on a vector with length from
+ // the last delimiter to the previous point, but in the direction of the average
+ // unit vector from delimiter to previous points.
+ // Using the averaged vector has the effect of "squaring off the curve",
+ // creating a sharper angle between the last motion and the preceding motion from
+ // the delimiter. In turn, this sharper angle achieves the splitting threshold
+ // even in a gentle curve.
+ PointF newDelimiter =
+ new PointF(
+ length * currentDX + lastDelimiter.x,
+ length * currentDY + lastDelimiter.y);
+
+ // Unit vector from newDelimiter to the most recent point
+ float nextDX = next.x - newDelimiter.x;
+ float nextDY = next.y - newDelimiter.y;
+ float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
+ nextDX = nextDX / nextLength;
+ nextDY = nextDY / nextLength;
+
+ // Compare the initial motion direction to the most recent motion direction,
+ // and segment the line if direction has changed by about 90 degrees.
+ float dot = currentDX * nextDX + currentDY * nextDY;
+ if (dot < ANGLE_THRESHOLD) {
+ path.add(newDelimiter);
+ lastDelimiter = newDelimiter;
+ dX = 0;
+ dY = 0;
+ count = 0;
+ }
+ }
+
+ // Vector from last delimiter to most recent point
+ float currentDX = next.x - lastDelimiter.x;
+ float currentDY = next.y - lastDelimiter.y;
+ length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
+
+ // Increment sum of unit vectors from delimiter to each following point
+ count = count + 1;
+ dX = dX + currentDX / length;
+ dY = dY + currentDY / length;
+ }
+
+ path.add(next);
+ if (DEBUG) {
+ Slog.d(getGestureName(), "path=" + path.toString());
+ }
+ // Classify line segments, and call Listener callbacks.
+ recognizeGesturePath(event, rawEvent, policyFlags, path);
+ }
+
+ /**
+ * Classifies a pair of line segments, by direction. Calls Listener callbacks for success or
+ * failure.
+ *
+ * @param event The raw motion event to pass to the listener's onGestureCanceled method.
+ * @param policyFlags Policy flags for the event.
+ * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
+ * @return true if the event is consumed, else false
+ */
+ private void recognizeGesturePath(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) {
+
+ final int displayId = event.getDisplayId();
+ if (path.size() != mDirections.length + 1) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ for (int i = 0; i < path.size() - 1; ++i) {
+ PointF start = path.get(i);
+ PointF end = path.get(i + 1);
+
+ float dX = end.x - start.x;
+ float dY = end.y - start.y;
+ int direction = toDirection(dX, dY);
+ if (direction != mDirections[i]) {
+ if (DEBUG) {
+ Slog.d(
+ getGestureName(),
+ "Found direction "
+ + directionToString(direction)
+ + " when expecting "
+ + directionToString(mDirections[i]));
+ }
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(getGestureName(), "Completed.");
+ }
+ completeGesture(event, rawEvent, policyFlags);
+ }
+
+ private static int toDirection(float dX, float dY) {
+ if (Math.abs(dX) > Math.abs(dY)) {
+ // Horizontal
+ return (dX < 0) ? LEFT : RIGHT;
+ } else {
+ // Vertical
+ return (dY < 0) ? UP : DOWN;
+ }
+ }
+
+ public static String directionToString(int direction) {
+ switch (direction) {
+ case LEFT:
+ return "left";
+ case RIGHT:
+ return "right";
+ case UP:
+ return "up";
+ case DOWN:
+ return "down";
+ default:
+ return "Unknown Direction";
+ }
+ }
+
+ @Override
+ String getGestureName() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Swipe ").append(directionToString(mDirections[0]));
+ for (int i = 1; i < mDirections.length; ++i) {
+ builder.append(" and ").append(directionToString(mDirections[i]));
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(super.toString());
+ if (getState() != STATE_GESTURE_CANCELED) {
+ builder.append(", mBaseX: ")
+ .append(mBaseX)
+ .append(", mBaseY: ")
+ .append(mBaseY)
+ .append(", mGestureDetectionThreshold:")
+ .append(mGestureDetectionThreshold)
+ .append(", mMinPixelsBetweenSamplesX:")
+ .append(mMinPixelsBetweenSamplesX)
+ .append(", mMinPixelsBetweenSamplesY:")
+ .append(mMinPixelsBetweenSamplesY);
+ }
+ return builder.toString();
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index b62e260aacad..5f4163880366 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -59,7 +59,7 @@ import java.util.List;
* @hide
*/
public class TouchExplorer extends BaseEventStreamTransformation
- implements AccessibilityGestureDetector.Listener {
+ implements GestureManifold.Listener {
static final boolean DEBUG = false;
@@ -104,7 +104,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
// Helper to detect gestures.
- private final AccessibilityGestureDetector mGestureDetector;
+ private final GestureManifold mGestureDetector;
// Helper class to track received pointers.
private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
@@ -142,7 +142,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
* one created in place, or for testing purpose.
*/
public TouchExplorer(Context context, AccessibilityManagerService service,
- AccessibilityGestureDetector detector) {
+ GestureManifold detector) {
mContext = context;
mAms = service;
mState = new TouchState();
@@ -161,7 +161,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
mDetermineUserIntentTimeout);
if (detector == null) {
- mGestureDetector = new AccessibilityGestureDetector(context, this);
+ mGestureDetector = new GestureManifold(context, this, mState);
} else {
mGestureDetector = detector;
}
@@ -285,7 +285,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
@Override
- public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
+ public void onDoubleTapAndHold() {
// Ignore the event if we aren't touch interacting.
if (!mState.isTouchInteracting()) {
return;
@@ -303,7 +303,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
@Override
- public boolean onDoubleTap(MotionEvent event, int policyFlags) {
+ public boolean onDoubleTap() {
if (!mState.isTouchInteracting()) {
return false;
}
@@ -319,7 +319,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
// Announce the end of a new touch interaction.
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-
+ mSendTouchInteractionEndDelayed.cancel();
// Try to use the standard accessibility API to click
if (!mAms.performActionOnAccessibilityFocusedItem(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
@@ -356,7 +356,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
@Override
- public boolean onGestureCancelled(MotionEvent event, int policyFlags) {
+ public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (mState.isGestureDetecting()) {
endGestureDetection(event.getActionMasked() == MotionEvent.ACTION_UP);
return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f463260a9d02..d23dbbefd325 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -71,7 +71,10 @@ public class TouchState {
// Helper class to track received pointers.
// Todo: collapse or hide this class so multiple classes don't modify it.
private final ReceivedPointerTracker mReceivedPointerTracker;
+ // The most recently received motion event.
private MotionEvent mLastReceivedEvent;
+ // The accompanying raw event without any transformations.
+ private MotionEvent mLastReceivedRawEvent;
public TouchState() {
mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -97,6 +100,9 @@ public class TouchState {
if (mLastReceivedEvent != null) {
mLastReceivedEvent.recycle();
}
+ if (mLastReceivedRawEvent != null) {
+ mLastReceivedRawEvent.recycle();
+ }
mLastReceivedEvent = MotionEvent.obtain(rawEvent);
mReceivedPointerTracker.onMotionEvent(rawEvent);
}
@@ -246,7 +252,6 @@ public class TouchState {
// or if it goes up the next one that most recently went down.
private int mPrimaryPointerId;
-
ReceivedPointerTracker() {
clear();
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d7ed2e9abde4..202f90068fc1 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -82,6 +82,7 @@ import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -168,6 +169,8 @@ final class AutofillManagerServiceImpl
@Nullable
private ServiceInfo mRemoteAugmentedAutofillServiceInfo;
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+
AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
AutofillCompatState autofillCompatState,
@@ -179,6 +182,7 @@ final class AutofillManagerServiceImpl
mUi = ui;
mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId);
mAutofillCompatState = autofillCompatState;
+ mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
updateLocked(disabled);
}
@@ -493,7 +497,7 @@ final class AutofillManagerServiceImpl
sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback,
mUiLatencyHistory, mWtfHistory, serviceComponentName,
componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly,
- flags);
+ flags, mInputMethodManagerInternal);
mSessions.put(newSession.id, newSession);
return newSession;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3b2da911a849..67bcccd1d7de 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -42,6 +42,8 @@ import android.app.IAssistDataReceiver;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -80,6 +82,8 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
+import android.util.Log;
+import android.util.Size;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -90,22 +94,38 @@ import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inline.InlinePresentationSpec;
+import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -290,6 +310,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private boolean mForAugmentedAutofillOnly;
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private CompletableFuture<IInlineSuggestionsResponseCallback>
+ mInlineSuggestionsResponseCallbackFuture;
+
+ @Nullable
+ private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
+
+ private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
+
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -386,7 +423,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ArrayList<FillContext> contexts =
mergePreviousSessionLocked(/* forSave= */ false);
- request = new FillRequest(requestId, contexts, mClientState, flags);
+
+ InlineSuggestionsRequest suggestionsRequest = null;
+ if (mSuggestionsRequestFuture != null) {
+ try {
+ suggestionsRequest = mSuggestionsRequestFuture.get(
+ INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Inline suggestions request cancelled");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ request = new FillRequest(requestId, contexts, mClientState, flags,
+ suggestionsRequest);
}
mRemoteFillService.onFillRequest(request);
@@ -569,6 +622,70 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
+ * Returns whether inline suggestions are enabled for Autofill.
+ */
+ // TODO(b/137800469): Implement this
+ private boolean isInlineSuggestionsEnabled() {
+ return true;
+ }
+
+ /**
+ * Ask the IME to make an inline suggestions request if enabled.
+ */
+ private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
+ int newState, int flags) {
+ if (isInlineSuggestionsEnabled()) {
+ mSuggestionsRequestFuture = new CompletableFuture<>();
+ mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
+
+ if (mInlineSuggestionsRequestCallback == null) {
+ mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallback(this);
+ }
+
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+ mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
+ }
+
+ requestNewFillResponseLocked(viewState, newState, flags);
+ }
+
+ private static final class InlineSuggestionsRequestCallback
+ extends IInlineSuggestionsRequestCallback.Stub {
+ private final WeakReference<Session> mSession;
+
+ private InlineSuggestionsRequestCallback(Session session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ Log.i(TAG, "inline suggestions request unsupported, "
+ + "falling back to regular autofill");
+ final Session session = mSession.get();
+ if (session != null) {
+ synchronized (session.mLock) {
+ session.mSuggestionsRequestFuture.cancel(true);
+ session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
+ }
+ }
+ }
+
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) throws RemoteException {
+ Log.i(TAG, "onInlineSuggestionsRequest() received: "
+ + request);
+ final Session session = mSession.get();
+ if (session != null) {
+ synchronized (session.mLock) {
+ session.mSuggestionsRequestFuture.complete(request);
+ session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
+ }
+ }
+ }
+ }
+
+ /**
* Reads a new structure and then request a new fill response from the fill service.
*/
@GuardedBy("mLock")
@@ -584,6 +701,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
triggerAugmentedAutofillLocked();
return;
}
+
viewState.setState(newState);
int requestId;
@@ -636,7 +754,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
@NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName,
@NonNull ComponentName componentName, boolean compatMode,
- boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) {
+ boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags,
+ @NonNull InputMethodManagerInternal inputMethodManagerInternal) {
if (sessionId < 0) {
wtf(null, "Non-positive sessionId: %s", sessionId);
}
@@ -661,6 +780,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
setClientLocked(client);
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
}
@@ -2208,7 +2329,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mForAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_RESTARTED_SESSION, flags);
return;
}
@@ -2218,7 +2340,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
+ viewState.getStateAsString());
}
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_STARTED_PARTITION, flags);
} else {
if (sVerbose) {
Slog.v(TAG, "Not starting new partition for view " + id + ": "
@@ -2325,7 +2448,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -2527,6 +2651,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
wtf(null, "onFillReady(): no service label or icon");
return;
}
+
+ final List<Slice> inlineSuggestionSlices = response.getInlineSuggestionSlices();
+ if (inlineSuggestionSlices != null) {
+ if (requestShowInlineSuggestions(inlineSuggestionSlices, response)) {
+ //TODO(b/137800469): Add logging instead of bypassing below logic.
+ return;
+ }
+ }
+
+
getUiForShowing().showFillUi(filledId, response, filterText,
mService.getServicePackageName(), mComponentName,
serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2558,6 +2692,109 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ /**
+ * Returns whether we made a request to show inline suggestions.
+ */
+ private boolean requestShowInlineSuggestions(List<Slice> inlineSuggestionSlices,
+ FillResponse response) {
+ IInlineSuggestionsResponseCallback inlineContentCallback = null;
+ synchronized (mLock) {
+ if (mInlineSuggestionsResponseCallbackFuture != null) {
+ try {
+ inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get(
+ INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Inline suggestions callback cancelled");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ if (inlineContentCallback == null) {
+ Log.w(TAG, "Session input method callback is not set yet");
+ return false;
+ }
+
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets == null) {
+ Log.w(TAG, "response returned null datasets");
+ return false;
+ }
+
+ final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
+ final int slicesSize = inlineSuggestionSlices.size();
+ if (datasets.size() < slicesSize) {
+ Log.w(TAG, "Too many slices provided, not enough corresponding datasets");
+ return false;
+ }
+
+ for (int sliceIndex = 0; sliceIndex < slicesSize; sliceIndex++) {
+ Log.i(TAG, "Reading slice-" + sliceIndex + " at requestshowinlinesuggestions");
+ final Slice inlineSuggestionSlice = inlineSuggestionSlices.get(sliceIndex);
+ final List<SliceItem> sliceItems = inlineSuggestionSlice.getItems();
+
+ final int itemsSize = sliceItems.size();
+ int minWidth = -1;
+ int maxWidth = -1;
+ int minHeight = -1;
+ int maxHeight = -1;
+ for (int itemIndex = 0; itemIndex < itemsSize; itemIndex++) {
+ final SliceItem item = sliceItems.get(itemIndex);
+ final String subtype = item.getSubType();
+ switch (item.getSubType()) {
+ case "SUBTYPE_MIN_WIDTH":
+ minWidth = item.getInt();
+ break;
+ case "SUBTYPE_MAX_WIDTH":
+ maxWidth = item.getInt();
+ break;
+ case "SUBTYPE_MIN_HEIGHT":
+ minHeight = item.getInt();
+ break;
+ case "SUBTYPE_MAX_HEIGHT":
+ maxHeight = item.getInt();
+ break;
+ default:
+ Log.i(TAG, "unrecognized inline suggestions subtype: " + subtype);
+ }
+ }
+
+ if (minWidth < 0 || maxWidth < 0 || minHeight < 0 || maxHeight < 0) {
+ Log.w(TAG, "missing inline suggestion requirements");
+ return false;
+ }
+
+ final InlinePresentationSpec spec = new InlinePresentationSpec.Builder(
+ new Size(minWidth, minHeight), new Size(maxWidth, maxHeight)).build();
+ final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+ spec, InlineSuggestionInfo.SOURCE_AUTOFILL, new String[] { "" });
+ final Dataset dataset = datasets.get(sliceIndex);
+
+ inlineSuggestions.add(new InlineSuggestion(inlineSuggestionInfo,
+ new IInlineContentProvider.Stub() {
+ @Override
+ public void provideContent(int width, int height,
+ IInlineContentCallback callback) throws RemoteException {
+ getUiForShowing().getSuggestionSurfaceForShowing(dataset, response,
+ mCurrentViewId, width, height, callback);
+ }
+ }));
+ }
+
+ try {
+ inlineContentCallback.onInlineSuggestionsResponse(
+ new InlineSuggestionsResponse(inlineSuggestions));
+ } catch (RemoteException e) {
+ Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()");
+ return false;
+ }
+
+ return true;
+ }
+
boolean isDestroyed() {
synchronized (mLock) {
return mDestroyed;
@@ -2831,6 +3068,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
+ // TODO(b/137800469): implement inlined suggestions for augmented autofill
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
currentValue);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 26bb7c35b9dc..eadfd31c27bf 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -24,6 +24,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.os.Bundle;
@@ -37,13 +39,19 @@ import android.service.autofill.ValueFinder;
import android.text.TextUtils;
import android.util.Slog;
import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
import android.view.autofill.IAutofillWindowPresenter;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.view.inline.IInlineContentCallback;
import com.android.server.LocalServices;
import com.android.server.UiModeManagerInternal;
import com.android.server.UiThread;
@@ -171,6 +179,75 @@ public final class AutoFillUI {
}
/**
+ * TODO(b/137800469): Fill in javadoc.
+ * TODO(b/137800469): peoperly manage lifecycle of suggestions surfaces.
+ */
+ public void getSuggestionSurfaceForShowing(@NonNull Dataset dataset,
+ @NonNull FillResponse response, AutofillId autofillId, int width, int height,
+ IInlineContentCallback cb) {
+ if (dataset == null) {
+ Slog.w(TAG, "getSuggestionSurfaceForShowing() called with null dataset");
+ }
+ mHandler.post(() -> {
+ final SurfaceControl suggestionSurface = inflateInlineSuggestion(dataset, response,
+ autofillId, width, height);
+
+ try {
+ cb.onContent(suggestionSurface);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException replying onContent(" + suggestionSurface + "): " + e);
+ }
+ });
+ }
+
+ /**
+ * TODO(b/137800469): Fill in javadoc, generate custom templated view for inline suggestions.
+ * TODO: Move to ExtServices.
+ *
+ * @return a {@link SurfaceControl} with the inflated content embedded in it.
+ */
+ private SurfaceControl inflateInlineSuggestion(@NonNull Dataset dataset,
+ @NonNull FillResponse response, AutofillId autofillId, int width, int height) {
+ Slog.i(TAG, "inflate() called");
+ final Context context = mContext;
+ final int index = dataset.getFieldIds().indexOf(autofillId);
+ if (index < 0) {
+ Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId
+ + " not found in dataset");
+ }
+
+ final AutofillValue datasetValue = dataset.getFieldValues().get(index);
+ final SurfaceControl sc = new SurfaceControl.Builder()
+ // TODO(b/137800469): sanitize name
+ .setName("af suggestion")
+ .build();
+
+ //TODO(b/137800469): Pass in inputToken from IME.
+ final WindowlessViewRoot wvr = new WindowlessViewRoot(context, context.getDisplay(), sc,
+ null);
+
+ TextView textView = new TextView(context);
+ textView.setText(datasetValue.getTextValue());
+ textView.setBackgroundColor(Color.WHITE);
+ textView.setTextColor(Color.BLACK);
+ textView.setOnClickListener(v -> {
+ Slog.d(TAG, "Inline suggestion clicked");
+ hideFillUiUiThread(mCallback, true);
+ if (mCallback != null) {
+ final int datasetIndex = response.getDatasets().indexOf(dataset);
+ mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+ }
+ });
+
+ WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ wvr.addView(textView, lp);
+
+ return sc;
+ }
+
+ /**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
* @param focusedId the currently focused field
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 203bc61c2022..b7adfa4a3ff1 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -116,6 +116,7 @@ java_library_static {
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
"netd_event_listener_interface-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 49046b244bb4..d2f1113ec5a9 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -133,6 +133,12 @@ public abstract class PackageManagerInternal {
@PackageInfoFlags int flags, int filterCallingUid, int userId);
/**
+ * Retrieve CE data directory inode number of an application.
+ * Return 0 if there's error.
+ */
+ public abstract long getCeDataInode(String packageName, int userId);
+
+ /**
* Return a List of all application packages that are installed on the
* device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
* set, a list of all applications including those deleted with
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
index d8a2fe35c7e8..861c731c69e0 100644
--- a/services/core/java/com/android/server/CountryDetectorService.java
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -16,14 +16,6 @@
package com.android.server;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashMap;
-
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.DumpUtils;
-import com.android.server.location.ComprehensiveCountryDetector;
-
import android.content.Context;
import android.location.Country;
import android.location.CountryListener;
@@ -36,17 +28,25 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.DumpUtils;
+import com.android.server.location.ComprehensiveCountryDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
/**
- * This class detects the country that the user is in through
- * {@link ComprehensiveCountryDetector}.
+ * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}.
*
* @hide
*/
-public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
+public class CountryDetectorService extends ICountryDetector.Stub {
/**
- * The class represents the remote listener, it will also removes itself
- * from listener list when the remote process was died.
+ * The class represents the remote listener, it will also removes itself from listener list when
+ * the remote process was died.
*/
private final class Receiver implements IBinder.DeathRecipient {
private final ICountryListener mListener;
@@ -79,9 +79,11 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
}
}
- private final static String TAG = "CountryDetector";
+ private static final String TAG = "CountryDetector";
- /** Whether to dump the state of the country detector service to bugreports */
+ /**
+ * Whether to dump the state of the country detector service to bugreports
+ */
private static final boolean DEBUG = false;
private final HashMap<IBinder, Receiver> mReceivers;
@@ -92,15 +94,21 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
private CountryListener mLocationBasedDetectorListener;
public CountryDetectorService(Context context) {
+ this(context, BackgroundThread.getHandler());
+ }
+
+ @VisibleForTesting
+ CountryDetectorService(Context context, Handler handler) {
super();
- mReceivers = new HashMap<IBinder, Receiver>();
+ mReceivers = new HashMap<>();
mContext = context;
+ mHandler = handler;
}
@Override
public Country detectCountry() {
if (!mSystemReady) {
- return null; // server not yet active
+ return null; // server not yet active
} else {
return mCountryDetector.detectCountry();
}
@@ -154,9 +162,8 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
}
}
-
protected void notifyReceivers(Country country) {
- synchronized(mReceivers) {
+ synchronized (mReceivers) {
for (Receiver receiver : mReceivers.values()) {
try {
receiver.getListener().onCountryDetected(country);
@@ -170,38 +177,23 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
void systemRunning() {
// Shall we wait for the initialization finish.
- BackgroundThread.getHandler().post(this);
+ mHandler.post(
+ () -> {
+ initialize();
+ mSystemReady = true;
+ });
}
private void initialize() {
mCountryDetector = new ComprehensiveCountryDetector(mContext);
- mLocationBasedDetectorListener = new CountryListener() {
- public void onCountryDetected(final Country country) {
- mHandler.post(new Runnable() {
- public void run() {
- notifyReceivers(country);
- }
- });
- }
- };
- }
-
- public void run() {
- mHandler = new Handler();
- initialize();
- mSystemReady = true;
+ mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
}
protected void setCountryListener(final CountryListener listener) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mCountryDetector.setCountryListener(listener);
- }
- });
+ mHandler.post(() -> mCountryDetector.setCountryListener(listener));
}
- // For testing
+ @VisibleForTesting
boolean isSystemReady() {
return mSystemReady;
}
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index c0f10a3c86e1..92d1da5be7a2 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
import android.Manifest;
import android.app.AppOpsManager;
import android.app.PendingIntent;
@@ -37,6 +39,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.service.carrier.CarrierMessagingService;
import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Slog;
@@ -523,11 +526,11 @@ public class MmsServiceBroker extends SystemService {
// Grant permission for the carrier app.
Intent intent = new Intent(action);
- TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- List<String> carrierPackages =
- telephonyManager.getCarrierPackageNamesForIntentAndPhone(
- intent, SubscriptionManager.getPhoneId(subId));
+ TelephonyManager telephonyManager = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ List<String> carrierPackages = telephonyManager
+ .getCarrierPackageNamesForIntentAndPhone(
+ intent, getPhoneIdFromSubId(subId));
if (carrierPackages != null && carrierPackages.size() == 1) {
LocalServices.getService(UriGrantsManagerInternal.class)
.grantUriPermissionFromIntent(callingUid, carrierPackages.get(0),
@@ -539,4 +542,13 @@ public class MmsServiceBroker extends SystemService {
return contentUri;
}
}
+
+ private int getPhoneIdFromSubId(int subId) {
+ SubscriptionManager subManager = (SubscriptionManager)
+ mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ if (subManager == null) return INVALID_SIM_SLOT_INDEX;
+ SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
+ if (info == null) return INVALID_SIM_SLOT_INDEX;
+ return info.getSimSlotIndex();
+ }
}
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index b0b45f411d34..39be311e902d 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -35,11 +35,11 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.TimeUtils;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
@@ -137,7 +137,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
private void registerForTelephonyIntents() {
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ intentFilter.addAction(TelephonyManager.ACTION_NETWORK_SET_TIME);
mContext.registerReceiver(mNitzReceiver, intentFilter);
}
@@ -247,7 +247,7 @@ public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeU
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DBG) Log.d(TAG, "Received " + action);
- if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
+ if (TelephonyManager.ACTION_NETWORK_SET_TIME.equals(action)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9b1d9e9a246c..22fa8ff4a0fa 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -118,6 +118,7 @@ import android.sysprop.VoldProperties;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DataUnit;
import android.util.FeatureFlagUtils;
@@ -143,6 +144,7 @@ import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.pm.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
@@ -366,6 +368,8 @@ class StorageManagerService extends IStorageManager.Stub
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ private final Installer mInstaller;
+
/** Holding lock for AppFuse business */
private final Object mAppFuseLock = new Object();
@@ -1244,6 +1248,13 @@ class StorageManagerService extends IStorageManager.Stub
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
+ mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume mounted " + vol, e);
+ }
}
}
@@ -1289,6 +1300,13 @@ class StorageManagerService extends IStorageManager.Stub
if (vol != null) {
mStorageSessionController.onVolumeRemove(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume unmounted " + vol, e);
+ }
}
}
};
@@ -1600,6 +1618,9 @@ class StorageManagerService extends IStorageManager.Stub
mStorageSessionController = new StorageSessionController(mContext, mIsFuseEnabled);
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
+
// Initialize the last-fstrim tracking if necessary
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
@@ -1973,6 +1994,13 @@ class StorageManagerService extends IStorageManager.Stub
try {
mVold.unmount(vol.id);
mStorageSessionController.onVolumeUnmount(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.e(TAG, "Failed unmount mirror data", e);
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3161,16 +3189,20 @@ class StorageManagerService extends IStorageManager.Stub
final boolean forWrite = (flags & StorageManager.FLAG_FOR_WRITE) != 0;
final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
+ final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
// Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
// are no guarantees that callers will see a consistent view of the volume before that
// point
final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
+ final boolean userIsDemo;
final boolean userKeyUnlocked;
final boolean storagePermission;
final long token = Binder.clearCallingIdentity();
try {
+ userIsDemo = LocalServices.getService(UserManagerInternal.class)
+ .getUserInfo(userId).isDemo();
userKeyUnlocked = isUserKeyUnlocked(userId);
storagePermission = mStorageManagerInternal.hasExternalStorage(uid, packageName);
} finally {
@@ -3180,6 +3212,7 @@ class StorageManagerService extends IStorageManager.Stub
boolean foundPrimary = false;
final ArrayList<StorageVolume> res = new ArrayList<>();
+ final ArraySet<String> resUuids = new ArraySet<>();
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final VolumeInfo vol = mVolumes.valueAt(i);
@@ -3222,7 +3255,43 @@ class StorageManagerService extends IStorageManager.Stub
} else {
res.add(userVol);
}
+ resUuids.add(userVol.getUuid());
}
+
+ if (includeRecent) {
+ final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+ for (int i = 0; i < mRecords.size(); i++) {
+ final VolumeRecord rec = mRecords.valueAt(i);
+
+ // Skip if we've already included it above
+ if (resUuids.contains(rec.fsUuid)) continue;
+
+ // Treat as recent if mounted within the last week
+ if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+ final StorageVolume userVol = rec.buildStorageVolume(mContext);
+ res.add(userVol);
+ resUuids.add(userVol.getUuid());
+ }
+ }
+ }
+ }
+
+ // Synthesize a volume for preloaded media under demo users, so that
+ // it's scanned into MediaStore
+ if (userIsDemo) {
+ final String id = "demo";
+ final File path = Environment.getDataPreloadsMediaDirectory();
+ final boolean primary = false;
+ final boolean removable = false;
+ final boolean emulated = true;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(userId);
+ final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
+ final String description = mContext.getString(android.R.string.unknownName);
+
+ res.add(new StorageVolume(id, path, path, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, id, envState));
}
if (!foundPrimary) {
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 5e659b64dbbe..c5409f85bde3 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -29,6 +29,10 @@ import android.os.UserManager;
import com.android.server.pm.UserManagerService;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* The base class for services running in the system process. Override and implement
* the lifecycle event callback methods as needed.
@@ -164,6 +168,25 @@ public abstract class SystemService {
}
/**
+ * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+ */
+ protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
+ final List<Integer> supportedUsers = new ArrayList<>(allUsers.size());
+ for (UserInfo user : allUsers) {
+ supportedUsers.add(user.id);
+ }
+ if (allUsers.isEmpty()) {
+ pw.print(prefix); pw.println("No supported users");
+ } else {
+ final int size = supportedUsers.size();
+ pw.print(prefix); pw.print(size); pw.print(" supported user");
+ if (size > 1) pw.print("s");
+ pw.print(": "); pw.println(supportedUsers);
+ }
+ }
+
+ /**
* @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
* calls this method).
*/
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4a0c7a33467d..13eb55665741 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
import static android.telephony.TelephonyRegistryManager.SIM_ACTIVATION_TYPE_DATA;
import static android.telephony.TelephonyRegistryManager.SIM_ACTIVATION_TYPE_VOICE;
@@ -31,7 +32,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -59,6 +59,7 @@ import android.telephony.PreciseDisconnectCause;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
@@ -97,7 +98,7 @@ import java.util.NoSuchElementException;
* and 15973975 by saving the phoneId of the registrant and then using the
* phoneId when deciding to to make a callback. This is necessary because
* a subId changes from to a dummy value when a SIM is removed and thus won't
- * compare properly. Because SubscriptionManager.getPhoneId(int subId) handles
+ * compare properly. Because getPhoneIdFromSubId(int subId) handles
* the dummy value conversion we properly do the callbacks.
*
* Eventually we may want to remove the notion of dummy value but for now this
@@ -131,7 +132,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+ int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
boolean matchPhoneStateListenerEvent(int events) {
return (callback != null) && ((events & this.events) != 0);
@@ -210,8 +211,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int[] mDataConnectionNetworkType;
- private int[] mOtaspMode;
-
private ArrayList<List<CellInfo>> mCellInfo = null;
private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
@@ -231,7 +230,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int mDefaultSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private int mDefaultPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+ private int mDefaultPhoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
private int[] mRingingCallState;
@@ -260,7 +259,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private final LocalLog mListenLog = new LocalLog(100);
- private PreciseDataConnectionState[] mPreciseDataConnectionState;
+ // Per-phoneMap of APN Type to DataConnectionState
+ private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+ new ArrayList<Map<String, PreciseDataConnectionState>>();
// Nothing here yet, but putting it here in case we want to add more in the future.
static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -358,7 +359,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
SubscriptionManager.getDefaultSubscriptionId());
int newDefaultPhoneId = intent.getIntExtra(
SubscriptionManager.EXTRA_SLOT_INDEX,
- SubscriptionManager.getPhoneId(newDefaultSubId));
+ getPhoneIdFromSubId(newDefaultSubId));
if (DBG) {
log("onReceive:current mDefaultSubId=" + mDefaultSubId
+ " current mDefaultPhoneId=" + mDefaultPhoneId
@@ -405,7 +406,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCallForwarding = copyOf(mCallForwarding, mNumPhones);
mCellLocation = copyOf(mCellLocation, mNumPhones);
mSrvccState = copyOf(mSrvccState, mNumPhones);
- mOtaspMode = copyOf(mOtaspMode, mNumPhones);
mPreciseCallState = copyOf(mPreciseCallState, mNumPhones);
mForegroundCallState = copyOf(mForegroundCallState, mNumPhones);
mBackgroundCallState = copyOf(mBackgroundCallState, mNumPhones);
@@ -415,7 +415,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
mCallAttributes = copyOf(mCallAttributes, mNumPhones);
- mPreciseDataConnectionState = copyOf(mPreciseDataConnectionState, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
@@ -423,6 +422,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
cutListToSize(mImsReasonInfo, mNumPhones);
+ cutListToSize(mPreciseDataConnectionStates, mNumPhones);
return;
}
@@ -443,7 +443,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
@@ -454,7 +453,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+ mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
}
// Note that location can be null for non-phone builds like
@@ -506,7 +505,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCallForwarding = new boolean[numPhones];
mCellLocation = new Bundle[numPhones];
mSrvccState = new int[numPhones];
- mOtaspMode = new int[numPhones];
mPreciseCallState = new PreciseCallState[numPhones];
mForegroundCallState = new int[numPhones];
mBackgroundCallState = new int[numPhones];
@@ -516,7 +514,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
mCallAttributes = new CallAttributes[numPhones];
- mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
+ mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>();
mImsReasonInfo = new ArrayList<>();
mEmergencyNumberList = new HashMap<>();
@@ -538,7 +536,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
@@ -549,7 +546,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+ mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
}
// Note that location can be null for non-phone builds like
@@ -768,7 +765,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
return;
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -880,13 +877,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
- if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
- try {
- r.callback.onOtaspChanged(mOtaspMode[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
try {
if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
@@ -922,8 +912,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
try {
- r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ for (PreciseDataConnectionState pdcs
+ : mPreciseDataConnectionStates.get(phoneId).values()) {
+ r.callback.onPreciseDataConnectionStateChanged(pdcs);
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -937,7 +929,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
try {
- r.callback.onVoiceActivationStateChanged(mVoiceActivationState[phoneId]);
+ r.callback.onVoiceActivationStateChanged(
+ mVoiceActivationState[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1099,7 +1092,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
// Called only by Telecomm to communicate call state across different phone accounts. So
// there is no need to add a valid subId or slotId.
broadcastCallStateChanged(state, phoneNumber,
- SubscriptionManager.INVALID_PHONE_INDEX,
+ SubscriptionManager.INVALID_SIM_SLOT_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
@@ -1324,7 +1317,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
synchronized (mRecords) {
mCarrierNetworkChangeState = active;
for (int subId : subIds) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
if (VDBG) {
log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
@@ -1357,7 +1350,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
log("notifyCellInfoForSubscriber: subId=" + subId
+ " cellInfo=" + cellInfo);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mCellInfo.set(phoneId, cellInfo);
@@ -1448,7 +1441,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
log("notifyCallForwardingChangedForSubscriber: subId=" + subId
+ " cfi=" + cfi);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mCallForwarding[phoneId] = cfi;
@@ -1476,7 +1469,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyDataActivity()" )) {
return;
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mDataActivity[phoneId] = state;
@@ -1496,20 +1489,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
- boolean isDataAllowed,
- String apn, String apnType,
- LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int networkType, boolean roaming) {
+ /**
+ * Send a notification to registrants that the data connection state has changed.
+ *
+ * @param phoneId the phoneId carrying the data connection
+ * @param subId the subscriptionId for the data connection
+ * @param apnType the APN type that triggered a change in the data connection
+ * @param preciseState a PreciseDataConnectionState that has info about the data connection
+ */
+ public void notifyDataConnectionForSubscriber(
+ int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
+
+ String apn = "";
+ int state = TelephonyManager.DATA_UNKNOWN;
+ int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ LinkProperties linkProps = null;
+
+ if (preciseState != null) {
+ apn = preciseState.getDataConnectionApn();
+ state = preciseState.getState();
+ networkType = preciseState.getNetworkType();
+ linkProps = preciseState.getDataConnectionLinkProperties();
+ }
if (VDBG) {
log("notifyDataConnectionForSubscriber: subId=" + subId
- + " state=" + state + " isDataAllowed=" + isDataAllowed
- + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
- + " mRecords.size()=" + mRecords.size());
+ + " state=" + state + "' apn='" + apn
+ + "' apnType=" + apnType + " networkType=" + networkType
+ + "' preciseState=" + preciseState);
}
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
// We only call the callback when the change is for default APN type.
@@ -1541,30 +1552,48 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mDataConnectionState[phoneId] = state;
mDataConnectionNetworkType[phoneId] = networkType;
}
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- state, networkType,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
- linkProperties, DataFailCause.NONE);
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+
+ boolean needsNotify = false;
+ // State has been cleared for this APN Type
+ if (preciseState == null) {
+ // We try clear the state and check if the state was previously not cleared
+ needsNotify = mPreciseDataConnectionStates.get(phoneId).remove(apnType) != null;
+ } else {
+ // We need to check to see if the state actually changed
+ PreciseDataConnectionState oldPreciseState =
+ mPreciseDataConnectionStates.get(phoneId).put(apnType, preciseState);
+ needsNotify = !preciseState.equals(oldPreciseState);
+ }
+
+ if (needsNotify) {
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onPreciseDataConnectionStateChanged(preciseState);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
}
handleRemoveListLocked();
}
- broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
- networkCapabilities, roaming, subId);
+
+ broadcastDataConnectionStateChanged(state, apn, apnType, subId);
}
- public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+ /**
+ * Stub to satisfy the ITelephonyRegistry aidl interface; do not use this function.
+ * @see #notifyDataConnectionFailedForSubscriber
+ */
+ public void notifyDataConnectionFailed(String apnType) {
+ loge("This function should not be invoked");
+ }
+
+ private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
@@ -1574,17 +1603,20 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
- DataFailCause.NONE);
+ mPreciseDataConnectionStates.get(phoneId).put(
+ apnType,
+ new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ DataFailCause.NONE));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ mPreciseDataConnectionStates.get(phoneId).get(apnType));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1611,7 +1643,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
log("notifyCellLocationForSubscriber: subId=" + subId
+ " cellLocation=" + cellLocation);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mCellLocation[phoneId] = cellLocation;
@@ -1635,29 +1667,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyOtaspChanged(int subId, int otaspMode) {
- if (!checkNotifyPermission("notifyOtaspChanged()" )) {
- return;
- }
- int phoneId = SubscriptionManager.getPhoneId(subId);
- synchronized (mRecords) {
- if (validatePhoneId(phoneId)) {
- mOtaspMode[phoneId] = otaspMode;
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- r.callback.onOtaspChanged(otaspMode);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
- }
- }
- }
- handleRemoveListLocked();
- }
- }
-
public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
int foregroundCallState, int backgroundCallState) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
@@ -1743,7 +1752,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyImsCallDisconnectCause()")) {
return;
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mImsReasonInfo.set(phoneId, imsReasonInfo);
@@ -1772,25 +1781,32 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
}
+
+ // precise notify invokes imprecise notify
+ notifyDataConnectionFailedForSubscriber(phoneId, subId, apnType);
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
+ mPreciseDataConnectionStates.get(phoneId).put(
+ apnType,
+ new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ failCause));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ mPreciseDataConnectionStates.get(phoneId).get(apnType));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
}
}
}
-
handleRemoveListLocked();
}
}
@@ -1803,7 +1819,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (VDBG) {
log("notifySrvccStateChanged: subId=" + subId + " srvccState=" + state);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mSrvccState[phoneId] = state;
@@ -2048,7 +2064,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
-
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2082,12 +2097,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println("mCellInfo=" + mCellInfo.get(i));
pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i));
pw.println("mSrvccState=" + mSrvccState[i]);
- pw.println("mOtaspMode=" + mOtaspMode[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
- pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+ pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
pw.decreaseIndent();
@@ -2211,7 +2225,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
}
// If the phoneId is invalid, the broadcast is for overall call state.
- if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+ if (phoneId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
}
@@ -2247,29 +2261,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- private void broadcastDataConnectionStateChanged(int state, boolean isDataAllowed, String apn,
- String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities,
- boolean roaming, int subId) {
+ private void broadcastDataConnectionStateChanged(int state, String apn,
+ String apnType, int subId) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
- if (!isDataAllowed) {
- intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
- }
- if (linkProperties != null) {
- intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
- String iface = linkProperties.getInterfaceName();
- if (iface != null) {
- intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
- }
- }
- if (networkCapabilities != null) {
- intent.putExtra(PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY, networkCapabilities);
- }
- if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
@@ -2684,4 +2682,18 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private static CallQuality createCallQuality() {
return new CallQuality(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
+
+ private int getPhoneIdFromSubId(int subId) {
+ SubscriptionManager subManager = (SubscriptionManager)
+ mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ if (subManager == null) return INVALID_SIM_SLOT_INDEX;
+
+ if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ subId = SubscriptionManager.getDefaultSubscriptionId();
+ }
+
+ SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
+ if (info == null) return INVALID_SIM_SLOT_INDEX;
+ return info.getSimSlotIndex();
+ }
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index f6c11cd1d0d6..76a8f92312ae 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -61,6 +61,7 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsLog;
@@ -162,6 +163,8 @@ public class VibratorService extends IVibratorService.Stub
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
+ private SparseArray<Pair<VibrationEffect, AudioAttributes>> mAlwaysOnEffects =
+ new SparseArray<>();
static native boolean vibratorExists();
static native void vibratorInit();
@@ -173,6 +176,8 @@ public class VibratorService extends IVibratorService.Stub
static native boolean vibratorSupportsExternalControl();
static native void vibratorSetExternalControl(boolean enabled);
static native long vibratorGetCapabilities();
+ static native void vibratorAlwaysOnEnable(long id, long effect, long strength);
+ static native void vibratorAlwaysOnDisable(long id);
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@@ -522,6 +527,41 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ @Override // Binder call
+ public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attrs) {
+ if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
+ throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
+ }
+ if ((mCapabilities & IVibrator.CAP_ALWAYS_ON_CONTROL) == 0) {
+ Slog.e(TAG, "Always-on effects not supported.");
+ return false;
+ }
+ if (effect == null) {
+ synchronized (mLock) {
+ mAlwaysOnEffects.delete(id);
+ vibratorAlwaysOnDisable(id);
+ }
+ } else {
+ if (!verifyVibrationEffect(effect)) {
+ return false;
+ }
+ if (!(effect instanceof VibrationEffect.Prebaked)) {
+ Slog.e(TAG, "Only prebaked effects supported for always-on.");
+ return false;
+ }
+ if (attrs == null) {
+ attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_UNKNOWN)
+ .build();
+ }
+ synchronized (mLock) {
+ mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
+ updateAlwaysOnLocked(id, effect, attrs);
+ }
+ }
+ return true;
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -992,6 +1032,8 @@ public class VibratorService extends IVibratorService.Stub
// If the state changes out from under us then just reset.
doCancelVibrateLocked();
}
+
+ updateAlwaysOnLocked();
}
}
@@ -1058,6 +1100,27 @@ public class VibratorService extends IVibratorService.Stub
mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
}
+ private void updateAlwaysOnLocked(int id, VibrationEffect effect, AudioAttributes attrs) {
+ // TODO: Check DND and LowPower settings
+ final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+ final int intensity = getCurrentIntensityLocked(vib);
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ vibratorAlwaysOnDisable(id);
+ } else {
+ final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ final int strength = intensityToEffectStrength(intensity);
+ vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
+ }
+ }
+
+ private void updateAlwaysOnLocked() {
+ for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
+ int id = mAlwaysOnEffects.keyAt(i);
+ Pair<VibrationEffect, AudioAttributes> pair = mAlwaysOnEffects.valueAt(i);
+ updateAlwaysOnLocked(id, pair.first, pair.second);
+ }
+ }
+
@Override
public void onInputDeviceAdded(int deviceId) {
updateVibrators();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e101fe0e192c..5996b7d848ea 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4409,7 +4409,6 @@ public class AccountManagerService
return true;
}
- @Override
public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
@@ -4425,7 +4424,6 @@ public class AccountManagerService
return r > 0;
}
- @Override
public boolean removeSharedAccountAsUser(Account account, int userId) {
return removeSharedAccountAsUser(account, userId, getCallingUid());
}
@@ -4443,7 +4441,6 @@ public class AccountManagerService
return deleted;
}
- @Override
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f1cee0348832..16096069631e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -957,10 +957,12 @@ public class ActivityManagerService extends IActivityManager.Stub
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(Properties properties) {
- mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0);
- if (DEBUG_PSS) {
- Slog.d(TAG_PSS, "Activity-start PSS delay now "
- + mPssDeferralTime + " ms");
+ if (properties.getKeyset().contains(ACTIVITY_START_PSS_DEFER_CONFIG)) {
+ mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0);
+ if (DEBUG_PSS) {
+ Slog.d(TAG_PSS, "Activity-start PSS delay now "
+ + mPssDeferralTime + " ms");
+ }
}
}
};
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5378f438fea5..557def44dc66 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.AppZygote;
@@ -78,6 +79,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
+import android.provider.DeviceConfig;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -85,6 +87,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -117,6 +120,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
+import java.util.Map;
/**
* Activity manager code dealing with processes.
@@ -124,6 +128,13 @@ import java.util.List;
public final class ProcessList {
static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk";
+
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
static final int MIN_CRASH_INTERVAL = 60 * 1000;
@@ -337,6 +348,8 @@ public final class ProcessList {
private boolean mOomLevelsSet = false;
+ private boolean mAppDataIsolationEnabled = false;
+
/**
* Temporary to avoid allocations. Protected by main lock.
*/
@@ -461,9 +474,7 @@ public final class ProcessList {
@GuardedBy("ProcessList.this.mService")
void freeIsolatedUidLocked(int uid) {
- // Strip out userId
- final int appId = UserHandle.getAppId(uid);
- mUidUsed.delete(appId);
+ mUidUsed.delete(uid);
}
};
@@ -623,6 +634,10 @@ public final class ProcessList {
mService = service;
mActiveUids = activeUids;
mPlatformCompat = platformCompat;
+ // Get this after boot, and won't be changed until it's rebooted, as we don't
+ // want some apps enabled while some apps disabled
+ mAppDataIsolationEnabled =
+ SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -1855,6 +1870,32 @@ public final class ProcessList {
}
}
+ private boolean shouldIsolateAppData(ProcessRecord app) {
+ if (!mAppDataIsolationEnabled) {
+ return false;
+ }
+ if (!UserHandle.isApp(app.uid)) {
+ return false;
+ }
+ final int minTargetSdk = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ ANDROID_APP_DATA_ISOLATION_MIN_SDK, Build.VERSION_CODES.R);
+ return app.info.targetSdkVersion >= minTargetSdk;
+ }
+
+ private Map<String, Pair<String, Long>> getPackageAppDataInfoMap(PackageManagerInternal pmInt,
+ String[] packages, int uid) {
+ Map<String, Pair<String, Long>> result = new ArrayMap<>(packages.length);
+ int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+ long inode = pmInt.getCeDataInode(packageName, userId);
+ if (inode != 0) {
+ result.put(packageName, Pair.create(volumeUuid, inode));
+ }
+ }
+ return result;
+ }
+
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
@@ -1871,6 +1912,21 @@ public final class ProcessList {
app.setHasForegroundActivities(true);
}
+ final Map<String, Pair<String, Long>> pkgDataInfoMap;
+
+ if (shouldIsolateAppData(app)) {
+ // Get all packages belongs to the same shared uid. sharedPackages is empty array
+ // if it doesn't have shared uid.
+ final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+ final String sharedUserId = pmInt.getSharedUserIdForPackage(app.info.packageName);
+ final String[] sharedPackages = pmInt.getPackagesForSharedUserId(sharedUserId,
+ app.userId);
+ pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
+ ? new String[]{app.info.packageName} : sharedPackages, uid);
+ } else {
+ pkgDataInfoMap = null;
+ }
+
final Process.ProcessStartResult startResult;
if (hostingRecord.usesWebviewZygote()) {
startResult = startWebView(entryPoint,
@@ -1886,13 +1942,13 @@ public final class ProcessList {
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges,
- new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
- app.mDisabledCompatChanges,
+ app.mDisabledCompatChanges, pkgDataInfoMap,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 232bc08e5b5c..fc67e24684bb 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -77,21 +77,28 @@ public class AttentionManagerService extends SystemService {
private static final String LOG_TAG = "AttentionManagerService";
private static final boolean DEBUG = false;
+ /** Service will unbind if connection is not used for that amount of time. */
+ private static final long CONNECTION_TTL_MILLIS = 60_000;
+
+ /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
- /** Service will unbind if connection is not used for that amount of time. */
- private static final long CONNECTION_TTL_MILLIS = 60_000;
+ /**
+ * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
+ * attention called within that period - cached value will be returned.
+ */
+ @VisibleForTesting static final String KEY_STALE_AFTER_MILLIS = "stale_after_millis";
- /** If the check attention called within that period - cached value will be returned. */
- private static final long STALE_AFTER_MILLIS = 5_000;
+ /** Default value in absence of {@link DeviceConfig} override. */
+ @VisibleForTesting static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
/** The size of the buffer that stores recent attention check results. */
@VisibleForTesting
protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;
- /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
- private static final String SERVICE_ENABLED = "service_enabled";
private static String sTestAttentionServicePackage;
private final Context mContext;
private final PowerManager mPowerManager;
@@ -160,11 +167,29 @@ public class AttentionManagerService extends SystemService {
@VisibleForTesting
protected boolean isServiceEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, SERVICE_ENABLED,
+ return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
DEFAULT_SERVICE_ENABLED);
}
/**
+ * How much time we consider a result fresh; if the check attention called within that period -
+ * cached value will be returned.
+ */
+ @VisibleForTesting
+ protected long getStaleAfterMillis() {
+ final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS,
+ DEFAULT_STALE_AFTER_MILLIS);
+
+ if (millis < 0 || millis > 10_000) {
+ Slog.w(LOG_TAG, "Bad flag value supplied for: " + KEY_STALE_AFTER_MILLIS);
+ return DEFAULT_STALE_AFTER_MILLIS;
+ }
+
+ return millis;
+ }
+
+ /**
* Checks whether user attention is at the screen and calls in the provided callback.
*
* Calling this multiple times quickly in a row will result in either a) returning a cached
@@ -199,7 +224,7 @@ public class AttentionManagerService extends SystemService {
// throttle frequent requests
final AttentionCheckCache cache = userState.mAttentionCheckCacheBuffer == null ? null
: userState.mAttentionCheckCacheBuffer.getLast();
- if (cache != null && now < cache.mLastComputed + STALE_AFTER_MILLIS) {
+ if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
return true;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index df5600473024..352d0d56ca8a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -125,7 +125,7 @@ public class AudioDeviceInventory {
+ " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
}
- String getKey() {
+ @NonNull String getKey() {
return makeDeviceListKey(mDeviceType, mDeviceAddress);
}
@@ -133,7 +133,7 @@ public class AudioDeviceInventory {
* Generate a unique key for the mConnectedDevices List by composing the device "type"
* and the "address" associated with a specific instance of that device type
*/
- private static String makeDeviceListKey(int device, String deviceAddress) {
+ @NonNull private static String makeDeviceListKey(int device, String deviceAddress) {
return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
}
}
@@ -726,8 +726,8 @@ public class AudioDeviceInventory {
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
mConnectedDevices.remove(deviceToRemoveKey);
- if (!mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)
- .equals(deviceToRemoveKey)) {
+ if (!deviceToRemoveKey
+ .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
// removing A2DP device not currently used by AudioPolicy, log but don't act on it
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 2de18c391d4a..60f0e8e6c63d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
@@ -1013,8 +1014,13 @@ public abstract class BiometricServiceBase extends SystemService
private boolean isForegroundActivity(int uid, int pid) {
try {
- List<ActivityManager.RunningAppProcessInfo> procs =
+ final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
+ if (procs == null) {
+ Slog.e(getTag(), "Processes null, defaulting to true");
+ return true;
+ }
+
int N = procs.size();
for (int i = 0; i < N; i++) {
ActivityManager.RunningAppProcessInfo proc = procs.get(i);
@@ -1206,6 +1212,11 @@ public abstract class BiometricServiceBase extends SystemService
* @return authenticator id for the calling user
*/
protected long getAuthenticatorId(String opPackageName) {
+ if (isKeyguard(opPackageName)) {
+ // If an app tells us it's keyguard, check that it actually is.
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ }
+
final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
return mAuthenticatorIds.getOrDefault(userId, 0L);
}
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
index 1b1a29293639..789551bb2263 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -19,6 +19,8 @@ package com.android.server.incremental;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.DataLoaderManager;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.os.Bundle;
@@ -27,8 +29,6 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.incremental.IIncrementalManager;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.os.incremental.IncrementalFileSystemControlParcel;
import android.util.Slog;
import java.io.FileDescriptor;
@@ -80,8 +80,8 @@ public class IncrementalManagerService extends IIncrementalManager.Stub {
* Finds data loader service provider and binds to it. This requires PackageManager.
*/
@Override
- public boolean prepareDataLoader(int mountId, IncrementalFileSystemControlParcel control,
- IncrementalDataLoaderParamsParcel params,
+ public boolean prepareDataLoader(int mountId, FileSystemControlParcel control,
+ DataLoaderParamsParcel params,
IDataLoaderStatusListener listener) {
Bundle dataLoaderParams = new Bundle();
dataLoaderParams.putCharSequence("packageName", params.packageName);
@@ -138,10 +138,6 @@ public class IncrementalManagerService extends IIncrementalManager.Stub {
Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId);
return;
}
- try {
- dataLoader.onFileCreated(inode, metadata);
- } catch (RemoteException ex) {
- }
}
@Override
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index d35e806b2685..6a8434aad88b 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -24,6 +24,7 @@ import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
@@ -31,7 +32,6 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ShellCommand;
-import android.os.incremental.IncrementalDataLoaderParams;
import android.util.Slog;
import java.io.FileDescriptor;
@@ -111,7 +111,7 @@ public final class IncrementalManagerShellCommand extends ShellCommand {
pw.println("File names and sizes don't match.");
return ERROR_DATA_LOADER_INIT;
}
- final IncrementalDataLoaderParams params = new IncrementalDataLoaderParams(
+ final DataLoaderParams params = new DataLoaderParams(
"", LOADER_PACKAGE_NAME, dataLoaderDynamicArgs);
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index d71ffb770cc3..58f6ba2fd67f 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -762,6 +762,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
if (mUpdatingPackageNames != null) {
pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
}
+ dumpSupportedUsers(pw, prefix);
if (mServiceNameResolver != null) {
pw.print(prefix); pw.print("Name resolver: ");
mServiceNameResolver.dumpShort(pw); pw.println();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 944a95dda99b..44c8971ba4c2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,8 +18,14 @@ package com.android.server.inputmethod;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodInfo;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.server.LocalServices;
import java.util.Collections;
@@ -57,6 +63,17 @@ public abstract class InputMethodManagerInternal {
public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
/**
+ * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
+ * the input method.
+ *
+ * @param componentName {@link ComponentName} of current app/activity.
+ * @param autofillId {@link AutofillId} of currently focused field.
+ * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
+ */
+ public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
+
+ /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
private static final InputMethodManagerInternal NOP =
@@ -78,6 +95,17 @@ public abstract class InputMethodManagerInternal {
public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
return Collections.emptyList();
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("IMManagerInternal", "RemoteException calling"
+ + " onInlineSuggestionsUnsupported: " + e);
+ }
+ }
};
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 471fa72b1957..252ee1d0a5e4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -109,6 +109,7 @@ import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -140,6 +141,7 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
@@ -211,6 +213,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final int MSG_SYSTEM_UNLOCK_USER = 5000;
+ static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
+
static final long TIME_TO_RECONNECT = 3 * 1000;
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -1785,6 +1789,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return settings.getEnabledInputMethodListLocked();
}
+ @GuardedBy("mMethodMap")
+ private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ if (mCurMethod != null) {
+ executeOrSendMessage(mCurMethod,
+ mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
+ componentName, autofillId, callback));
+ }
+ }
+
/**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
@@ -3435,7 +3449,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
/**
- * This is kept due to {@link android.annotation.UnsupportedAppUsage} in
+ * This is kept due to {@link android.compat.annotation.UnsupportedAppUsage} in
* {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
* {@link InputMethodService#onCreate()}.
*
@@ -3874,6 +3888,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final int userId = msg.arg1;
onUnlockUser(userId);
return true;
+
+ // ---------------------------------------------------------------
+ case MSG_INLINE_SUGGESTIONS_REQUEST:
+ args = (SomeArgs) msg.obj;
+ final ComponentName componentName = (ComponentName) args.arg2;
+ final AutofillId autofillId = (AutofillId) args.arg3;
+ final IInlineSuggestionsRequestCallback callback =
+ (IInlineSuggestionsRequestCallback) args.arg4;
+ try {
+ ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(componentName,
+ autofillId, callback);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
+ }
+ return true;
}
return false;
}
@@ -4434,6 +4463,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ private void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ synchronized (mMethodMap) {
+ onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+ }
+ }
+
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
@@ -4464,6 +4500,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
return mService.getEnabledInputMethodListAsUser(userId);
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
}
@BinderThread
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 02e29e0b2ab5..c13d55adda08 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -59,10 +59,12 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputChannel;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
import android.view.inputmethod.InputMethodInfo;
@@ -82,6 +84,7 @@ import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -187,6 +190,18 @@ public final class MultiClientInputMethodManagerService {
@UserIdInt int userId) {
return userIdToInputMethodInfoMapper.getAsList(userId);
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ //TODO(b/137800469): support multi client IMEs.
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("MultiClientIMManager", "RemoteException calling"
+ + " onInlineSuggestionsUnsupported: " + e);
+ }
+ }
});
}
diff --git a/services/core/java/com/android/server/integrity/OWNERS b/services/core/java/com/android/server/integrity/OWNERS
index 019aa4fb0f2b..55a4e409c767 100644
--- a/services/core/java/com/android/server/integrity/OWNERS
+++ b/services/core/java/com/android/server/integrity/OWNERS
@@ -3,4 +3,3 @@ khelmy@google.com
mdchurchill@google.com
sturla@google.com
songpan@google.com
-bjy@google.com
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java
deleted file mode 100644
index 4d3961df6092..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.server.integrity.serializer;
-
-import android.annotation.IntDef;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.Formula;
-import android.content.integrity.Rule;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/** A helper class for identifying the indexing type of a given rule. */
-public class RuleIndexTypeIdentifier {
-
- static final int NOT_INDEXED = 0;
- static final int PACKAGE_NAME_INDEXED = 1;
- static final int APP_CERTIFICATE_INDEXED = 2;
-
- /** Represents which indexed file the rule should be located. */
- @IntDef(
- value = {
- NOT_INDEXED,
- PACKAGE_NAME_INDEXED,
- APP_CERTIFICATE_INDEXED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface IndexType {
- }
-
- /** Determines the indexing file type that a given rule should be located at. */
- public static int getIndexType(Rule rule) {
- if (rule == null) {
- throw new IllegalArgumentException("Indexing type cannot be determined for null rule.");
- }
- return getIndexType(rule.getFormula());
- }
-
- private static int getIndexType(Formula formula) {
- if (formula == null) {
- throw new IllegalArgumentException(
- "Indexing type cannot be determined for null formula.");
- }
-
- switch (formula.getTag()) {
- case Formula.COMPOUND_FORMULA_TAG:
- return getIndexTypeForCompoundFormula((CompoundFormula) formula);
- case Formula.STRING_ATOMIC_FORMULA_TAG:
- return getIndexTypeForAtomicStringFormula((AtomicFormula) formula);
- case Formula.INT_ATOMIC_FORMULA_TAG:
- case Formula.BOOLEAN_ATOMIC_FORMULA_TAG:
- // Package name and app certificate related formulas are string atomic formulas.
- return NOT_INDEXED;
- default:
- throw new IllegalArgumentException(
- String.format("Invalid formula tag type: %s", formula.getTag()));
- }
- }
-
- private static int getIndexTypeForCompoundFormula(CompoundFormula compoundFormula) {
- int connector = compoundFormula.getConnector();
- List<Formula> formulas = compoundFormula.getFormulas();
-
- switch (connector) {
- case CompoundFormula.NOT:
- // Having a NOT operator in the indexing messes up the indexing; e.g., deny
- // installation if app certificate is NOT X (should not be indexed with app cert
- // X). We will not keep these rules indexed.
- return NOT_INDEXED;
- case CompoundFormula.AND:
- case CompoundFormula.OR:
- Set<Integer> indexingTypesForAllFormulas =
- formulas.stream()
- .map(formula -> getIndexType(formula))
- .collect(Collectors.toSet());
- if (indexingTypesForAllFormulas.contains(PACKAGE_NAME_INDEXED)) {
- return PACKAGE_NAME_INDEXED;
- } else if (indexingTypesForAllFormulas.contains(APP_CERTIFICATE_INDEXED)) {
- return APP_CERTIFICATE_INDEXED;
- } else {
- return NOT_INDEXED;
- }
- default:
- return NOT_INDEXED;
- }
- }
-
- private static int getIndexTypeForAtomicStringFormula(AtomicFormula atomicFormula) {
- switch (atomicFormula.getKey()) {
- case AtomicFormula.PACKAGE_NAME:
- return PACKAGE_NAME_INDEXED;
- case AtomicFormula.APP_CERTIFICATE:
- return APP_CERTIFICATE_INDEXED;
- default:
- return NOT_INDEXED;
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
new file mode 100644
index 000000000000..dd871e2bbe6c
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.integrity.serializer;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Holds the indexing type and indexing key of a given formula. */
+class RuleIndexingDetails {
+
+ static final int NOT_INDEXED = 0;
+ static final int PACKAGE_NAME_INDEXED = 1;
+ static final int APP_CERTIFICATE_INDEXED = 2;
+
+ /** Represents which indexed file the rule should be located. */
+ @IntDef(
+ value = {
+ NOT_INDEXED,
+ PACKAGE_NAME_INDEXED,
+ APP_CERTIFICATE_INDEXED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndexType {
+ }
+
+ private @IndexType int mIndexType;
+ private String mRuleKey;
+
+ /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
+ RuleIndexingDetails(@IndexType int indexType) {
+ this.mIndexType = indexType;
+ this.mRuleKey = null;
+ }
+
+ /** Constructor with a ruleKey for indexed rules. */
+ RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
+ this.mIndexType = indexType;
+ this.mRuleKey = ruleKey;
+ }
+
+ /** Returns the indexing type for the rule. */
+ @IndexType
+ public int getIndexType() {
+ return mIndexType;
+ }
+
+ /** Returns the identified rule key. */
+ public String getRuleKey() {
+ return mRuleKey;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
new file mode 100644
index 000000000000..61dddf09a80a
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -0,0 +1,150 @@
+/*
+ * 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.server.integrity.serializer;
+
+import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
+
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
+import android.content.integrity.Rule;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/** A helper class for identifying the indexing type and key of a given rule. */
+class RuleIndexingDetailsIdentifier {
+
+ private static final String DEFAULT_RULE_KEY = "N/A";
+
+ /**
+ * Splits a given rule list into three indexing categories.
+ *
+ * TODO(b/145488708): Instead of this structure, sort the values in the map and just return a
+ * sorted list.
+ */
+ public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
+ List<Rule> rules) {
+ if (rules == null) {
+ throw new IllegalArgumentException(
+ "Index buckets cannot be created for null rule list.");
+ }
+
+ Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
+ typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
+ typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap());
+ typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap());
+
+ for (Rule rule : rules) {
+ RuleIndexingDetails indexingDetails = getIndexingDetails(rule.getFormula());
+ String ruleKey =
+ indexingDetails.getIndexType() != NOT_INDEXED
+ ? indexingDetails.getRuleKey()
+ : DEFAULT_RULE_KEY;
+
+ if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) {
+ typeOrganizedRuleMap
+ .get(indexingDetails.getIndexType())
+ .put(ruleKey, new ArrayList());
+ }
+
+ typeOrganizedRuleMap
+ .get(indexingDetails.getIndexType())
+ .get(ruleKey)
+ .add(rule);
+ }
+
+ return typeOrganizedRuleMap;
+ }
+
+ private static RuleIndexingDetails getIndexingDetails(Formula formula) {
+ switch (formula.getTag()) {
+ case Formula.COMPOUND_FORMULA_TAG:
+ return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
+ case Formula.STRING_ATOMIC_FORMULA_TAG:
+ return getIndexingDetailsForStringAtomicFormula(
+ (AtomicFormula.StringAtomicFormula) formula);
+ case Formula.INT_ATOMIC_FORMULA_TAG:
+ case Formula.BOOLEAN_ATOMIC_FORMULA_TAG:
+ // Package name and app certificate related formulas are string atomic formulas.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ default:
+ throw new IllegalArgumentException(
+ String.format("Invalid formula tag type: %s", formula.getTag()));
+ }
+ }
+
+ private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
+ CompoundFormula compoundFormula) {
+ int connector = compoundFormula.getConnector();
+ List<Formula> formulas = compoundFormula.getFormulas();
+
+ switch (connector) {
+ case CompoundFormula.AND:
+ case CompoundFormula.OR:
+ // If there is a package name related atomic rule, return package name indexed.
+ Optional<RuleIndexingDetails> packageNameRule =
+ formulas.stream()
+ .map(formula -> getIndexingDetails(formula))
+ .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
+ == PACKAGE_NAME_INDEXED)
+ .findAny();
+ if (packageNameRule.isPresent()) {
+ return packageNameRule.get();
+ }
+
+ // If there is an app certificate related atomic rule but no package name related
+ // atomic rule, return app certificate indexed.
+ Optional<RuleIndexingDetails> appCertificateRule =
+ formulas.stream()
+ .map(formula -> getIndexingDetails(formula))
+ .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
+ == APP_CERTIFICATE_INDEXED)
+ .findAny();
+ if (appCertificateRule.isPresent()) {
+ return appCertificateRule.get();
+ }
+
+ // Do not index when there is not package name or app certificate indexing.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ default:
+ // Having a NOT operator in the indexing messes up the indexing; e.g., deny
+ // installation if app certificate is NOT X (should not be indexed with app cert
+ // X). We will not keep these rules indexed.
+ // Also any other type of unknown operators will not be indexed.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ }
+ }
+
+ private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
+ AtomicFormula.StringAtomicFormula atomicFormula) {
+ switch (atomicFormula.getKey()) {
+ case AtomicFormula.PACKAGE_NAME:
+ return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
+ case AtomicFormula.APP_CERTIFICATE:
+ return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
+ default:
+ return new RuleIndexingDetails(NOT_INDEXED);
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 9fcee50d7037..e7b88604db32 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -183,8 +183,9 @@ class MediaRouter2ServiceImpl {
}
public void setControlCategories(@NonNull IMediaRouter2Client client,
- @Nullable List<String> categories) {
+ @NonNull List<String> categories) {
Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(categories, "categories must not be null");
final long token = Binder.clearCallingIdentity();
try {
@@ -390,8 +391,11 @@ class MediaRouter2ServiceImpl {
private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
if (clientRecord != null) {
- clientRecord.mControlCategories = categories;
+ if (clientRecord.mControlCategories.equals(categories)) {
+ return;
+ }
+ clientRecord.mControlCategories = categories;
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 7098435ca299..a7e40cbc5b66 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -59,6 +59,12 @@ public class NotificationComparator
return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
}
+ // If a score has been assigned by notification assistant service, use this service
+ // rank results within each bucket instead of this comparator implementation.
+ if (left.getRankingScore() != right.getRankingScore()) {
+ return -1 * Float.compare(left.getRankingScore(), right.getRankingScore());
+ }
+
// first all colorized notifications
boolean leftImportantColorized = isImportantColorized(left);
boolean rightImportantColorized = isImportantColorized(right);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e968fb700ca7..c8afcc97d5b1 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -144,6 +144,7 @@ public final class NotificationRecord {
private int mSystemImportance = IMPORTANCE_UNSPECIFIED;
private int mAssistantImportance = IMPORTANCE_UNSPECIFIED;
private int mImportance = IMPORTANCE_UNSPECIFIED;
+ private float mRankingScore = 0f;
// Field used in global sort key to bypass normal notifications
private int mCriticality = CriticalNotificationExtractor.NORMAL;
// A MetricsEvent.NotificationImportanceExplanation, tracking source of mImportance.
@@ -655,6 +656,9 @@ public final class NotificationRecord {
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
}
+ if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
+ mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
@@ -772,6 +776,10 @@ public final class NotificationRecord {
return mImportance;
}
+ public float getRankingScore() {
+ return mRankingScore;
+ }
+
public CharSequence getImportanceExplanation() {
switch (mImportanceExplanationCode) {
case MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN:
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index e05511681ba8..ac3bf9ad5d8a 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -26,6 +26,7 @@ import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
import android.text.TextUtils;
+import android.util.Pair;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -44,6 +45,38 @@ public class OverlayActorEnforcer {
private final VerifyCallback mVerifyCallback;
+ /**
+ * @return nullable actor result with {@link ActorState} failure status
+ */
+ static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
+ Map<String, Map<String, String>> namedActors) {
+ if (namedActors.isEmpty()) {
+ return Pair.create(null, ActorState.NO_NAMED_ACTORS);
+ }
+
+ Uri actorUri = Uri.parse(actorUriString);
+
+ String actorScheme = actorUri.getScheme();
+ List<String> actorPathSegments = actorUri.getPathSegments();
+ if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
+ return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME);
+ }
+
+ String actorNamespace = actorUri.getAuthority();
+ Map<String, String> namespace = namedActors.get(actorNamespace);
+ if (namespace == null) {
+ return Pair.create(null, ActorState.MISSING_NAMESPACE);
+ }
+
+ String actorName = actorPathSegments.get(0);
+ String packageName = namespace.get(actorName);
+ if (TextUtils.isEmpty(packageName)) {
+ return Pair.create(null, ActorState.MISSING_ACTOR_NAME);
+ }
+
+ return Pair.create(packageName, ActorState.ALLOWED);
+ }
+
public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) {
mVerifyCallback = verifyCallback;
}
@@ -141,31 +174,14 @@ public class OverlayActorEnforcer {
}
}
- Map<String, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
- if (namedActors.isEmpty()) {
- return ActorState.NO_NAMED_ACTORS;
- }
-
- Uri actorUri = Uri.parse(actor);
-
- String actorScheme = actorUri.getScheme();
- List<String> actorPathSegments = actorUri.getPathSegments();
- if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
- return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME;
- }
-
- String actorNamespace = actorUri.getAuthority();
- Map<String, String> namespace = namedActors.get(actorNamespace);
- if (namespace == null) {
- return ActorState.MISSING_NAMESPACE;
- }
-
- String actorName = actorPathSegments.get(0);
- String packageName = namespace.get(actorName);
- if (TextUtils.isEmpty(packageName)) {
- return ActorState.MISSING_ACTOR_NAME;
+ Map<String, Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
+ Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
+ ActorState actorUriState = actorUriPair.second;
+ if (actorUriState != ActorState.ALLOWED) {
+ return actorUriState;
}
+ String packageName = actorUriPair.first;
PackageInfo packageInfo = mVerifyCallback.getPackageInfo(packageName, userId);
if (packageInfo == null) {
return ActorState.MISSING_APP_INFO;
@@ -192,7 +208,7 @@ public class OverlayActorEnforcer {
* For easier logging/debugging, a set of all possible failure/success states when running
* enforcement.
*/
- private enum ActorState {
+ enum ActorState {
ALLOWED,
INVALID_ACTOR,
MISSING_NAMESPACE,
@@ -244,7 +260,7 @@ public class OverlayActorEnforcer {
* value maps actor name to package name
*/
@NonNull
- Map<String, ? extends Map<String, String>> getNamedActors();
+ Map<String, Map<String, String>> getNamedActors();
/**
* @return true if the target package has declared an overlayable
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8b69946873d8..f1947ac15645 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1073,8 +1073,6 @@ public final class OverlayManagerService extends SystemService {
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
- // TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried
- // to enforce visibility/other permission checks
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
final boolean useCache) {
if (useCache) {
@@ -1097,18 +1095,12 @@ public final class OverlayManagerService extends SystemService {
@Override
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
- try {
- return getPackageInfo(packageName, userId, true);
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
+ return getPackageInfo(packageName, userId, true);
}
@NonNull
@Override
- public Map<String, ? extends Map<String, String>> getNamedActors() {
+ public Map<String, Map<String, String>> getNamedActors() {
return SystemConfig.getInstance().getNamedActors();
}
@@ -1136,57 +1128,45 @@ public final class OverlayManagerService extends SystemService {
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@Nullable String targetOverlayableName, int userId)
throws IOException {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
- try {
- PackageInfo packageInfo = getPackageInfo(packageName, userId);
- if (packageInfo == null) {
- throw new IOException("Unable to get target package");
- }
+ PackageInfo packageInfo = getPackageInfo(packageName, userId);
+ if (packageInfo == null) {
+ throw new IOException("Unable to get target package");
+ }
- String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+ String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(baseCodePath);
- return apkAssets.getOverlayableInfo(targetOverlayableName);
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(baseCodePath);
+ return apkAssets.getOverlayableInfo(targetOverlayableName);
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
}
}
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws RemoteException, IOException {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
- try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
- userId);
- String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
+ userId);
+ String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(baseCodePath);
- return apkAssets.definesOverlayable();
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(baseCodePath);
+ return apkAssets.definesOverlayable();
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
}
}
@@ -1229,16 +1209,10 @@ public final class OverlayManagerService extends SystemService {
@Nullable
@Override
public String[] getPackagesForUid(int uid) {
- // TODO(b/143096091): Remove clearing calling ID
- long callingIdentity = Binder.clearCallingIdentity();
try {
- try {
- return mPackageManager.getPackagesForUid(uid);
- } catch (RemoteException ignored) {
- return null;
- }
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
+ return mPackageManager.getPackagesForUid(uid);
+ } catch (RemoteException ignored) {
+ return null;
}
}
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
new file mode 100644
index 000000000000..8bea119f3490
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -0,0 +1,375 @@
+/*
+ * 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.server.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.parsing.AndroidPackage;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageSetting;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Track visibility of a targets and overlays to actors.
+ *
+ * 4 cases to handle:
+ * <ol>
+ * <li>Target adds/changes an overlayable to add a reference to an actor
+ * <ul>
+ * <li>Must expose target to actor</li>
+ * <li>Must expose any overlays that pointed to that overlayable name to the actor</li>
+ * </ul>
+ * </li>
+ * <li>Target removes/changes an overlayable to remove a reference to an actor
+ * <ul>
+ * <li>If this target has no other overlayables referencing the actor, hide the
+ * target</li>
+ * <li>For all overlays targeting this overlayable, if the overlay is only visible to
+ * the actor through this overlayable, hide the overlay</li>
+ * </ul>
+ * </li>
+ * <li>Overlay adds/changes an overlay tag to add a reference to an overlayable name
+ * <ul>
+ * <li>Expose this overlay to the actor defined by the target overlayable</li>
+ * </ul>
+ * </li>
+ * <li>Overlay removes/changes an overlay tag to remove a reference to an overlayable name
+ * <ul>
+ * <li>If this overlay is only visible to an actor through this overlayable name's
+ * target's actor</li>
+ * </ul>
+ * </li>
+ * </ol>
+ *
+ * In this class, the names "actor", "target", and "overlay" all refer to the ID representations.
+ * All other use cases are named appropriate. "actor" is actor name, "target" is target package
+ * name, and "overlay" is overlay package name.
+ */
+public class OverlayReferenceMapper {
+
+ private final Object mLock = new Object();
+
+ /**
+ * Keys are actors, values are maps which map target to a set of overlays targeting it.
+ * The presence of a target in the value map means the actor and targets are connected, even
+ * if the corresponding target's set is empty.
+ * See class comment for specific types.
+ */
+ @GuardedBy("mLock")
+ private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+
+ /**
+ * Keys are actor package names, values are generic package names the actor should be able
+ * to see.
+ */
+ @GuardedBy("mLock")
+ private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+
+ @GuardedBy("mLock")
+ private boolean mDeferRebuild;
+
+ @NonNull
+ private final Provider mProvider;
+
+ /**
+ * @param deferRebuild whether or not to defer rebuild calls on add/remove until first get call;
+ * useful during boot when multiple packages are added in rapid succession
+ * and queries in-between are not expected
+ */
+ public OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider) {
+ this.mDeferRebuild = deferRebuild;
+ this.mProvider = provider != null ? provider : new Provider() {
+ @Nullable
+ @Override
+ public String getActorPkg(String actor) {
+ Map<String, Map<String, String>> namedActors = SystemConfig.getInstance()
+ .getNamedActors();
+
+ Pair<String, OverlayActorEnforcer.ActorState> actorPair =
+ OverlayActorEnforcer.getPackageNameForActor(actor, namedActors);
+ return actorPair.first;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
+ String target = pkg.getOverlayTarget();
+ if (TextUtils.isEmpty(target)) {
+ return Collections.emptyMap();
+ }
+
+ String overlayable = pkg.getOverlayTargetName();
+ Map<String, Set<String>> targetToOverlayables = new HashMap<>();
+ Set<String> overlayables = new HashSet<>();
+ overlayables.add(overlayable);
+ targetToOverlayables.put(target, overlayables);
+ return targetToOverlayables;
+ }
+ };
+ }
+
+ /**
+ * @return mapping of actor package to a set of packages it can view
+ */
+ @NonNull
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public Map<String, Set<String>> getActorPkgToPkgs() {
+ return mActorPkgToPkgs;
+ }
+
+ public boolean isValidActor(@NonNull String targetName, @NonNull String actorPackageName) {
+ synchronized (mLock) {
+ assertMapBuilt();
+ Set<String> validSet = mActorPkgToPkgs.get(actorPackageName);
+ return validSet != null && validSet.contains(targetName);
+ }
+ }
+
+ /**
+ * Add a package to be considered for visibility. Currently supports adding as a target and/or
+ * an overlay. Adding an actor is not supported. Those are configured as part of
+ * {@link SystemConfig#getNamedActors()}.
+ *
+ * @param pkg the package to add
+ * @param otherPkgs map of other packages to consider, excluding {@param pkg}
+ */
+ public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+ synchronized (mLock) {
+ if (!pkg.getOverlayables().isEmpty()) {
+ addTarget(pkg, otherPkgs);
+ }
+
+ // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
+ if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
+ addOverlay(pkg, otherPkgs);
+ }
+
+ if (!mDeferRebuild) {
+ rebuild();
+ }
+ }
+ }
+
+ /**
+ * Removes a package to be considered for visibility. Currently supports removing as a target
+ * and/or an overlay. Removing an actor is not supported. Those are staticly configured as part
+ * of {@link SystemConfig#getNamedActors()}.
+ *
+ * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+ */
+ public void removePkg(String pkgName) {
+ synchronized (mLock) {
+ removeTarget(pkgName);
+ removeOverlay(pkgName);
+
+ if (!mDeferRebuild) {
+ rebuild();
+ }
+ }
+ }
+
+ private void removeTarget(String target) {
+ synchronized (mLock) {
+ Iterator<Map<String, Set<String>>> iterator =
+ mActorToTargetToOverlays.values().iterator();
+ while (iterator.hasNext()) {
+ Map<String, Set<String>> next = iterator.next();
+ next.remove(target);
+ if (next.isEmpty()) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Associate an actor with an association of a new target to overlays for that target.
+ *
+ * If a target overlays itself, it will not be associated with itself, as only one half of the
+ * relationship needs to exist for visibility purposes.
+ */
+ private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+ synchronized (mLock) {
+ String target = targetPkg.getPackageName();
+ removeTarget(target);
+
+ Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
+ for (String overlayable : overlayablesToActors.keySet()) {
+ String actor = overlayablesToActors.get(overlayable);
+ addTargetToMap(actor, target);
+
+ for (AndroidPackage overlayPkg : otherPkgs.values()) {
+ Map<String, Set<String>> targetToOverlayables =
+ mProvider.getTargetToOverlayables(overlayPkg);
+ Set<String> overlayables = targetToOverlayables.get(target);
+ if (CollectionUtils.isEmpty(overlayables)) {
+ continue;
+ }
+
+ if (overlayables.contains(overlayable)) {
+ addOverlayToMap(actor, target, overlayPkg.getPackageName());
+ }
+ }
+ }
+ }
+ }
+
+ private void removeOverlay(String overlay) {
+ synchronized (mLock) {
+ for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
+ for (Set<String> overlays : targetToOverlays.values()) {
+ overlays.remove(overlay);
+ }
+ }
+ }
+ }
+
+ /**
+ * Associate an actor with an association of targets to overlays for a new overlay.
+ *
+ * If an overlay targets itself, it will not be associated with itself, as only one half of the
+ * relationship needs to exist for visibility purposes.
+ */
+ private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+ synchronized (mLock) {
+ String overlay = overlayPkg.getPackageName();
+ removeOverlay(overlay);
+
+ Map<String, Set<String>> targetToOverlayables =
+ mProvider.getTargetToOverlayables(overlayPkg);
+ for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
+ String target = entry.getKey();
+ Set<String> overlayables = entry.getValue();
+ AndroidPackage targetPkg = otherPkgs.get(target);
+ if (targetPkg == null) {
+ continue;
+ }
+
+ String targetPkgName = targetPkg.getPackageName();
+ Map<String, String> overlayableToActor = targetPkg.getOverlayables();
+ for (String overlayable : overlayables) {
+ String actor = overlayableToActor.get(overlayable);
+ if (TextUtils.isEmpty(actor)) {
+ continue;
+ }
+ addOverlayToMap(actor, targetPkgName, overlay);
+ }
+ }
+ }
+ }
+
+ public void rebuildIfDeferred() {
+ synchronized (mLock) {
+ if (mDeferRebuild) {
+ rebuild();
+ mDeferRebuild = false;
+ }
+ }
+ }
+
+ private void assertMapBuilt() {
+ if (mDeferRebuild) {
+ throw new IllegalStateException("The actor map must be built by calling "
+ + "rebuildIfDeferred before it is queried");
+ }
+ }
+
+ private void rebuild() {
+ synchronized (mLock) {
+ mActorPkgToPkgs.clear();
+ for (String actor : mActorToTargetToOverlays.keySet()) {
+ String actorPkg = mProvider.getActorPkg(actor);
+ if (TextUtils.isEmpty(actorPkg)) {
+ continue;
+ }
+
+ Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ Set<String> pkgs = new HashSet<>();
+
+ for (String target : targetToOverlays.keySet()) {
+ Set<String> overlays = targetToOverlays.get(target);
+ pkgs.add(target);
+ pkgs.addAll(overlays);
+ }
+
+ mActorPkgToPkgs.put(actorPkg, pkgs);
+ }
+ }
+ }
+
+ private void addTargetToMap(String actor, String target) {
+ Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ if (targetToOverlays == null) {
+ targetToOverlays = new HashMap<>();
+ mActorToTargetToOverlays.put(actor, targetToOverlays);
+ }
+
+ Set<String> overlays = targetToOverlays.get(target);
+ if (overlays == null) {
+ overlays = new HashSet<>();
+ targetToOverlays.put(target, overlays);
+ }
+ }
+
+ private void addOverlayToMap(String actor, String target, String overlay) {
+ synchronized (mLock) {
+ Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ if (targetToOverlays == null) {
+ targetToOverlays = new HashMap<>();
+ mActorToTargetToOverlays.put(actor, targetToOverlays);
+ }
+
+ Set<String> overlays = targetToOverlays.get(target);
+ if (overlays == null) {
+ overlays = new HashSet<>();
+ targetToOverlays.put(target, overlays);
+ }
+
+ overlays.add(overlay);
+ }
+ }
+
+ public interface Provider {
+
+ /**
+ * Given the actor string from an overlayable definition, return the actor's package name.
+ */
+ @Nullable
+ String getActorPkg(String actor);
+
+ /**
+ * Mock response of multiple overlay tags.
+ *
+ * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
+ */
+ @NonNull
+ Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 8374ee63e07e..c4bcf809a67d 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -16,8 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.PackageParser.Component;
-import static android.content.pm.PackageParser.IntentInfo;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
@@ -26,7 +24,6 @@ import android.annotation.Nullable;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
import android.content.pm.parsing.AndroidPackage;
import android.content.pm.parsing.ComponentParseUtils;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
@@ -50,9 +47,9 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.FgThread;
+import com.android.server.om.OverlayReferenceMapper;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -109,11 +106,16 @@ public class AppsFilter {
private final FeatureConfig mFeatureConfig;
+ private final OverlayReferenceMapper mOverlayReferenceMapper;
+
AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist,
- boolean systemAppsQueryable) {
+ boolean systemAppsQueryable,
+ @Nullable OverlayReferenceMapper.Provider overlayProvider) {
mFeatureConfig = featureConfig;
mForceQueryableByDevicePackageNames = forceQueryableWhitelist;
mSystemAppsQueryable = systemAppsQueryable;
+ mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
+ overlayProvider);
}
public interface FeatureConfig {
@@ -193,7 +195,7 @@ public class AppsFilter {
}
}
return new AppsFilter(featureConfig, forcedQueryablePackageNames,
- forceSystemAppsQueryable);
+ forceSystemAppsQueryable, null);
}
/** Returns true if the querying package may query for the potential target package */
@@ -282,6 +284,7 @@ public class AppsFilter {
public void onSystemReady() {
mFeatureConfig.onSystemReady();
+ mOverlayReferenceMapper.rebuildIfDeferred();
}
/**
@@ -338,6 +341,16 @@ public class AppsFilter {
}
}
}
+
+ int existingSize = existingSettings.size();
+ ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize);
+ for (int index = 0; index < existingSize; index++) {
+ PackageSetting pkgSetting = existingSettings.valueAt(index);
+ if (pkgSetting.pkg != null) {
+ existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
+ }
+ }
+ mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -381,6 +394,8 @@ public class AppsFilter {
addPackage(setting.sharedUser.packages.valueAt(i), existingSettings);
}
}
+
+ mOverlayReferenceMapper.removePkg(setting.name);
}
/**
@@ -397,8 +412,7 @@ public class AppsFilter {
PackageSetting targetPkgSetting, int userId) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
try {
- if (!shouldFilterApplicationInternal(callingUid, callingSetting,
- targetPkgSetting,
+ if (!shouldFilterApplicationInternal(callingUid, callingSetting, targetPkgSetting,
userId)) {
return false;
}
@@ -412,8 +426,8 @@ public class AppsFilter {
}
}
- private boolean shouldFilterApplicationInternal(int callingUid,
- SettingBase callingSetting, PackageSetting targetPkgSetting, int userId) {
+ private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting,
+ PackageSetting targetPkgSetting, int userId) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
try {
final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
@@ -530,6 +544,29 @@ public class AppsFilter {
}
}
}
+
+ if (callingSharedPkgSettings != null) {
+ int size = callingSharedPkgSettings.size();
+ for (int index = 0; index < size; index++) {
+ PackageSetting pkgSetting = callingSharedPkgSettings.valueAt(index);
+ if (mOverlayReferenceMapper.isValidActor(targetName, pkgSetting.name)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting,
+ "matches shared user of package that acts on target of "
+ + "overlay");
+ }
+ return false;
+ }
+ }
+ } else {
+ if (mOverlayReferenceMapper.isValidActor(targetName, callingPkgSetting.name)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
+ }
+ return false;
+ }
+ }
+
return true;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 26cd42daa9f8..eb4b5939aaad 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -580,6 +580,30 @@ public class Installer extends SystemService {
}
}
+ /**
+ * Bind mount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeMounted(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * Unmount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeRemoved(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeRemoved(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
if (!checkBeforeRemote()) return false;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 286d291836ec..0acf7c8080a2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -57,6 +57,7 @@ import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -1003,6 +1004,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
+ private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+ @Override
+ public void writeData(String name, long offsetBytes, long lengthBytes,
+ ParcelFileDescriptor incomingFd) {
+ if (incomingFd == null) {
+ throw new IllegalArgumentException("incomingFd can't be null");
+ }
+ try {
+ doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+ }
+
private class ChildStatusIntentReceiver {
private final SparseIntArray mChildSessionsRemaining;
private final IntentSender mStatusReceiver;
@@ -2356,6 +2372,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (mIncrementalFileStorages != null) {
try {
mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
+ //TODO(b/136132412): merge incremental and callback installation schemes
+ return;
} catch (IOException ex) {
throw new IllegalStateException(
"Failed to add and configure Incremental File: " + name, ex);
@@ -2405,7 +2423,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
- FilesystemConnector connector = new FilesystemConnector();
+ FileSystemConnector connector = new FileSystemConnector();
FileInfo[] addedFiles = mFiles.stream().filter(
file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
@@ -2430,19 +2448,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- // TODO(b/146080380): implement DataLoader using Incremental infrastructure.
- class FilesystemConnector {
- void writeData(FileInfo fileInfo, long offset, long lengthBytes,
- ParcelFileDescriptor incomingFd) throws IOException {
- doWriteInternal(fileInfo.name, offset, lengthBytes, incomingFd);
- }
- }
-
static class DataLoader {
private ParcelFileDescriptor mInFd = null;
- private FilesystemConnector mConnector = null;
+ private FileSystemConnector mConnector = null;
- void onCreate(FilesystemConnector connector) throws IOException {
+ void onCreate(FileSystemConnector connector) throws IOException {
mConnector = connector;
}
@@ -2460,14 +2470,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return false;
}
ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
- mConnector.writeData(fileInfo, 0, fileInfo.lengthBytes, inFd);
+ mConnector.writeData(fileInfo.name, 0, fileInfo.lengthBytes, inFd);
} else {
File localFile = new File(filePath);
ParcelFileDescriptor incomingFd = null;
try {
incomingFd = ParcelFileDescriptor.open(localFile,
ParcelFileDescriptor.MODE_READ_ONLY);
- mConnector.writeData(fileInfo, 0, localFile.length(), incomingFd);
+ mConnector.writeData(fileInfo.name, 0, localFile.length(), incomingFd);
} finally {
IoUtils.closeQuietly(incomingFd);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1153fb521c53..cb362b0f0a23 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17962,14 +17962,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
mPermissionManager.resetRuntimePermissions(pkg, nextUserId);
- // Also delete contributed media, when requested
- if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
- try {
- MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId));
- } catch (IOException e) {
- Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e);
- }
- }
}
if (outInfo != null) {
@@ -22634,6 +22626,18 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public long getCeDataInode(String packageName, int userId) {
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ return ps.getCeDataInode(userId);
+ }
+ Slog.e(TAG, "failed to find package " + packageName);
+ return 0;
+ }
+ }
+
+ @Override
public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
synchronized (mLock) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5fabdb618f44..3515cb71cd59 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -84,7 +84,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IntArray;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -1510,7 +1509,7 @@ public class UserManagerService extends IUserManager.Stub {
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
checkManageUsersPermission("update users");
if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
- Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
+ Slog.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
return;
}
mLocalService.setUserIcon(userId, bitmap);
@@ -1558,7 +1557,7 @@ public class UserManagerService extends IUserManager.Stub {
return ParcelFileDescriptor.open(
new File(iconPath), ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find icon file", e);
+ Slog.e(LOG_TAG, "Couldn't find icon file", e);
}
return null;
}
@@ -1656,7 +1655,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
if (DBG) {
- Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+ Slog.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+ " originatingUserId=" + originatingUserId
+ " global=" + global + (globalChanged ? " (changed)" : "")
+ " local=" + local + (localChanged ? " (changed)" : "")
@@ -1718,7 +1717,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mRestrictionsLock")
private void invalidateEffectiveUserRestrictionsLR(@UserIdInt int userId) {
if (DBG) {
- Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+ Slog.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
}
mCachedEffectiveUserRestrictions.remove(userId);
}
@@ -1940,7 +1939,7 @@ public class UserManagerService extends IUserManager.Stub {
try {
mAppOpsService.setUserRestrictions(effective, mUserRestriconToken, userId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+ Slog.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
}
}
});
@@ -2012,7 +2011,7 @@ public class UserManagerService extends IUserManager.Stub {
try {
runningUsers = ActivityManager.getService().getRunningUserIds();
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to access ActivityManagerService");
+ Slog.w(LOG_TAG, "Unable to access ActivityManagerService");
return;
}
// Then re-calculate the effective restrictions and apply, only for running users.
@@ -2596,7 +2595,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
} catch (Resources.NotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
+ Slog.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
}
if (!restrictions.isEmpty()) {
@@ -3056,7 +3055,7 @@ public class UserManagerService extends IUserManager.Stub {
? UserManager.DISALLOW_ADD_MANAGED_PROFILE
: UserManager.DISALLOW_ADD_USER;
if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) {
- Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
+ Slog.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
return null;
}
return createUserInternalUnchecked(name, userType, flags, parentId,
@@ -3115,7 +3114,7 @@ public class UserManagerService extends IUserManager.Stub {
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm.isMemoryLow()) {
- Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
+ Slog.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
return null;
}
@@ -3138,36 +3137,36 @@ public class UserManagerService extends IUserManager.Stub {
if (parent == null) return null;
}
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
- Log.e(LOG_TAG, "Cannot add more users of type " + userType
+ Slog.e(LOG_TAG, "Cannot add more users of type " + userType
+ ". Maximum number of that type already exists.");
return null;
}
// TODO(b/142482943): Perhaps let the following code apply to restricted users too.
if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
- Log.e(LOG_TAG, "Cannot add more profiles of type " + userType
+ Slog.e(LOG_TAG, "Cannot add more profiles of type " + userType
+ " for user " + parentId);
return null;
}
if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
// If we're not adding a guest/demo user or a profile and the 'user limit' has
// been reached, cannot add a user.
- Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
+ Slog.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
return null;
}
// In legacy mode, restricted profile's parent can only be the owner user
if (isRestricted && !UserManager.isSplitSystemUser()
&& (parentId != UserHandle.USER_SYSTEM)) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
return null;
}
if (isRestricted && UserManager.isSplitSystemUser()) {
if (parent == null) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ "specified");
return null;
}
if (!parent.info.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ Slog.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ "created for the specified parent user id " + parentId);
return null;
}
@@ -3318,7 +3317,7 @@ public class UserManagerService extends IUserManager.Stub {
+ Integer.toHexString(preCreatedUser.flags) + ").");
return null;
}
- Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+ Slog.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+ userType + " and bestowing on it flags " + UserInfo.flagsToString(flags));
preCreatedUser.name = name;
preCreatedUser.flags = newFlags;
@@ -3483,7 +3482,7 @@ public class UserManagerService extends IUserManager.Stub {
checkManageUsersPermission("Only the system can remove users");
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
UserManager.DISALLOW_REMOVE_USER, false)) {
- Log.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
+ Slog.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
return false;
}
@@ -3535,7 +3534,7 @@ public class UserManagerService extends IUserManager.Stub {
String restriction = isManagedProfile
? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
- Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
+ Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
return false;
}
return removeUserUnchecked(userId);
@@ -3553,25 +3552,25 @@ public class UserManagerService extends IUserManager.Stub {
final UserData userData;
int currentUser = ActivityManager.getCurrentUser();
if (currentUser == userId) {
- Log.w(LOG_TAG, "Current user cannot be removed.");
+ Slog.w(LOG_TAG, "Current user cannot be removed.");
return false;
}
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
userData = mUsers.get(userId);
if (userId == UserHandle.USER_SYSTEM) {
- Log.e(LOG_TAG, "System user cannot be removed.");
+ Slog.e(LOG_TAG, "System user cannot be removed.");
return false;
}
if (userData == null) {
- Log.e(LOG_TAG, String.format(
+ Slog.e(LOG_TAG, String.format(
"Cannot remove user %d, invalid user id provided.", userId));
return false;
}
if (mRemovingUserIds.get(userId)) {
- Log.e(LOG_TAG, String.format(
+ Slog.e(LOG_TAG, String.format(
"User %d is already scheduled for removal.", userId));
return false;
}
@@ -3591,7 +3590,7 @@ public class UserManagerService extends IUserManager.Stub {
try {
mAppOpsService.removeUser(userId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
+ Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
}
// TODO(b/142482943): Send some sort of broadcast for profiles even if non-managed?
@@ -3616,7 +3615,7 @@ public class UserManagerService extends IUserManager.Stub {
}
});
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Failed to stop user during removal.", e);
+ Slog.w(LOG_TAG, "Failed to stop user during removal.", e);
return false;
}
return res == ActivityManager.USER_OP_SUCCESS;
@@ -3828,7 +3827,7 @@ public class UserManagerService extends IUserManager.Stub {
readEntry(restrictions, values, parser);
}
} catch (IOException|XmlPullParserException e) {
- Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+ Slog.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -4862,8 +4861,8 @@ public class UserManagerService extends IUserManager.Stub {
}
private static void debug(String message) {
- Log.d(LOG_TAG, message +
- (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
+ Slog.d(LOG_TAG, message
+ + (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
}
/** @see #getMaxUsersOfTypePerParent(UserTypeDetails) */
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index c36b9938a8cd..77bb48eadc41 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -67,10 +67,11 @@ import java.util.Set;
* then:
* <ul>
* <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
- * for all users</li>
- * <li>Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users</li>
- * <li>Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly
- * whitelisted so that it can be used for local development purposes.</li>
+ * for <b>all</b> users</li>
+ * <li>Otherwise, if {@link #isImplicitWhitelistSystemMode()}, the package is implicitly treated
+ * as whitelisted for the <b>{@link UserHandle#USER_SYSTEM}</b> user (not other users),
+ * which is useful for local development purposes</li>
+ * <li>Otherwise, the package is implicitly treated as blacklisted for all users</li>
* </ul>
*
* <p><b>NOTE:</b> the {@code SystemConfig} state is only updated on first boot or after a system
@@ -86,22 +87,24 @@ class UserSystemPackageInstaller {
* System Property whether to only install system packages on a user if they're whitelisted for
* that user type. These are flags and can be freely combined.
* <ul>
- * <li> 0 (0b0000) - disable whitelist (install all system packages; no logging)</li>
- * <li> 1 (0b0001) - enforce (only install system packages if they are whitelisted)</li>
- * <li> 2 (0b0010) - log (log when a non-whitelisted package is run)</li>
- * <li> 4 (0b0100) - implicitly whitelist any package not mentioned in the whitelist</li>
- * <li> 8 (0b1000) - ignore OTAs (don't install system packages during OTAs)</li>
- * <li>-1 - use device default (as defined in res/res/values/config.xml)</li>
+ * <li> 0 - disable whitelist (install all system packages; no logging)</li>
+ * <li> 1 - enforce (only install system packages if they are whitelisted)</li>
+ * <li> 2 - log (log when a non-whitelisted package is run)</li>
+ * <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li>
+ * <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li>
+ * <li> 16 - ignore OTAs (don't install system packages during OTAs)</li>
+ * <li>-1 - use device default (as defined in res/res/values/config.xml)</li>
* </ul>
* Note: This list must be kept current with config_userTypePackageWhitelistMode in
* frameworks/base/core/res/res/values/config.xml
*/
static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b0001;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b0010;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b0100;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0b1000;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10;
static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
@IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
@@ -281,6 +284,14 @@ class UserSystemPackageInstaller {
return isImplicitWhitelistMode(getWhitelistMode());
}
+ /**
+ * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
+ * whitelisted for the SYSTEM user.
+ */
+ boolean isImplicitWhitelistSystemMode() {
+ return isImplicitWhitelistSystemMode(getWhitelistMode());
+ }
+
/** See {@link #isEnforceMode()}. */
private static boolean isEnforceMode(int whitelistMode) {
return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
@@ -301,6 +312,11 @@ class UserSystemPackageInstaller {
return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0;
}
+ /** See {@link #isImplicitWhitelistSystemMode()}. */
+ private static boolean isImplicitWhitelistSystemMode(int whitelistMode) {
+ return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM) != 0;
+ }
+
/** Gets the PackageWhitelistMode for use of {@link #mWhitelistedPackagesForUserTypes}. */
private @PackageWhitelistMode int getWhitelistMode() {
final int runtimeMode = SystemProperties.getInt(
@@ -332,8 +348,8 @@ class UserSystemPackageInstaller {
if (!isEnforceMode(mode)) {
return null;
}
- final boolean isSystemUser = mUm.isUserTypeSubtypeOfSystem(userType);
- final boolean isImplicitWhitelistMode = isImplicitWhitelistMode(mode);
+ final boolean implicitlyWhitelist = isImplicitWhitelistMode(mode)
+ || (isImplicitWhitelistSystemMode(mode) && mUm.isUserTypeSubtypeOfSystem(userType));
final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(userType);
final Set<String> installPackages = new ArraySet<>();
@@ -343,7 +359,7 @@ class UserSystemPackageInstaller {
return;
}
if (shouldInstallPackage(pkg, mWhitelistedPackagesForUserTypes,
- whitelistedPackages, isImplicitWhitelistMode, isSystemUser)) {
+ whitelistedPackages, implicitlyWhitelist)) {
// Although the whitelist uses manifest names, this function returns packageNames.
installPackages.add(pkg.getPackageName());
}
@@ -360,31 +376,18 @@ class UserSystemPackageInstaller {
* installed. This is only used for overriding the userWhitelist in
* certain situations (based on its keyset).
* @param userWhitelist set of package manifest names that should be installed on this
- * particular user. This must be consistent with userTypeWhitelist, but is
- * passed in separately to avoid repeatedly calculating it from
+ * <b>particular</b> user. This must be consistent with userTypeWhitelist,
+ * but is passed in separately to avoid repeatedly calculating it from
* userTypeWhitelist.
- * @param isImplicitWhitelistMode whether non-mentioned packages are implicitly whitelisted.
- * @param isSystemUser whether the user is USER_SYSTEM (which gets special treatment).
+ * @param implicitlyWhitelist whether non-mentioned packages are implicitly whitelisted.
*/
@VisibleForTesting
static boolean shouldInstallPackage(AndroidPackage sysPkg,
@NonNull ArrayMap<String, Long> userTypeWhitelist,
- @NonNull Set<String> userWhitelist, boolean isImplicitWhitelistMode,
- boolean isSystemUser) {
-
+ @NonNull Set<String> userWhitelist, boolean implicitlyWhitelist) {
final String pkgName = sysPkg.getManifestPackageName();
- boolean install = (isImplicitWhitelistMode && !userTypeWhitelist.containsKey(pkgName))
+ return (implicitlyWhitelist && !userTypeWhitelist.containsKey(pkgName))
|| userWhitelist.contains(pkgName);
-
- // For the purposes of local development, any package that isn't even mentioned in the
- // whitelist at all is implicitly treated as whitelisted for the SYSTEM user.
- if (!install && isSystemUser && !userTypeWhitelist.containsKey(pkgName)) {
- install = true;
- Slog.e(TAG, "System package " + pkgName + " is not mentioned "
- + "in SystemConfig's 'install-in-user-type' but we are "
- + "implicitly treating it as whitelisted for the SYSTEM user.");
- }
- return install;
}
/**
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 9324870904d9..6cdfcff61415 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -58,6 +58,7 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.pm.Installer;
@@ -1008,11 +1009,19 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
installerPackageName) == PackageManager.PERMISSION_GRANTED;
// For now only allow rollbacks for modules or for testing.
- return (isModule(packageName) && manageRollbacksGranted)
+ return (isRollbackWhitelisted(packageName) && manageRollbacksGranted)
|| testManageRollbacksGranted;
}
/**
+ * Returns true is this package is eligible for enabling rollback.
+ */
+ private boolean isRollbackWhitelisted(String packageName) {
+ // TODO: Remove #isModule when the white list is ready.
+ return SystemConfig.getInstance().getRollbackWhitelistedPackages().contains(packageName)
+ || isModule(packageName);
+ }
+ /**
* Returns true if the package name is the name of a module.
*/
private boolean isModule(String packageName) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
new file mode 100644
index 000000000000..3fa52301d9a0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+/**
+ * An implementation of SoundTriggerMiddlewareImpl.AudioSessionProvider that ties to native
+ * AudioSystem module via JNI.
+ */
+class AudioSessionProviderImpl extends SoundTriggerMiddlewareImpl.AudioSessionProvider {
+ @Override
+ public native AudioSession acquireSession();
+
+ @Override
+ public native void releaseSession(int sessionHandle);
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
new file mode 100644
index 000000000000..9b22f33a20b0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -0,0 +1,390 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
+ * types.
+ *
+ * @hide
+ */
+class ConversionUtil {
+ static @NonNull
+ SoundTriggerModuleProperties hidl2aidlProperties(
+ @NonNull ISoundTriggerHw.Properties hidlProperties) {
+ SoundTriggerModuleProperties aidlProperties = new SoundTriggerModuleProperties();
+ aidlProperties.implementor = hidlProperties.implementor;
+ aidlProperties.description = hidlProperties.description;
+ aidlProperties.version = hidlProperties.version;
+ aidlProperties.uuid = hidl2aidlUuid(hidlProperties.uuid);
+ aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
+ aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
+ aidlProperties.maxUsers = hidlProperties.maxUsers;
+ aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+ aidlProperties.captureTransition = hidlProperties.captureTransition;
+ aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
+ aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
+ aidlProperties.triggerInEvent = hidlProperties.triggerInEvent;
+ aidlProperties.powerConsumptionMw = hidlProperties.powerConsumptionMw;
+ return aidlProperties;
+ }
+
+ static @NonNull
+ String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
+ if (hidlUuid.node == null || hidlUuid.node.length != 6) {
+ throw new IllegalArgumentException("UUID.node must be of length 6.");
+ }
+ return String.format(UuidUtil.FORMAT,
+ hidlUuid.timeLow,
+ hidlUuid.timeMid,
+ hidlUuid.versionAndTimeHigh,
+ hidlUuid.variantAndClockSeqHigh,
+ hidlUuid.node[0],
+ hidlUuid.node[1],
+ hidlUuid.node[2],
+ hidlUuid.node[3],
+ hidlUuid.node[4],
+ hidlUuid.node[5]);
+ }
+
+ static @NonNull
+ Uuid aidl2hidlUuid(@NonNull String aidlUuid) {
+ Matcher matcher = UuidUtil.PATTERN.matcher(aidlUuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Illegal format for UUID: " + aidlUuid);
+ }
+ Uuid hidlUuid = new Uuid();
+ hidlUuid.timeLow = Integer.parseUnsignedInt(matcher.group(1), 16);
+ hidlUuid.timeMid = (short) Integer.parseUnsignedInt(matcher.group(2), 16);
+ hidlUuid.versionAndTimeHigh = (short) Integer.parseUnsignedInt(matcher.group(3), 16);
+ hidlUuid.variantAndClockSeqHigh = (short) Integer.parseUnsignedInt(matcher.group(4), 16);
+ hidlUuid.node = new byte[]{(byte) Integer.parseUnsignedInt(matcher.group(5), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(6), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(7), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(8), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(9), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(10), 16)};
+ return hidlUuid;
+ }
+
+ static int aidl2hidlSoundModelType(int aidlType) {
+ switch (aidlType) {
+ case SoundModelType.GENERIC:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC;
+ case SoundModelType.KEYPHRASE:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + aidlType);
+ }
+ }
+
+ static int hidl2aidlSoundModelType(int hidlType) {
+ switch (hidlType) {
+ case android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC:
+ return SoundModelType.GENERIC;
+ case android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE:
+ return SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + hidlType);
+ }
+ }
+
+ static @NonNull
+ ISoundTriggerHw.Phrase aidl2hidlPhrase(@NonNull Phrase aidlPhrase) {
+ ISoundTriggerHw.Phrase hidlPhrase = new ISoundTriggerHw.Phrase();
+ hidlPhrase.id = aidlPhrase.id;
+ hidlPhrase.recognitionModes = aidl2hidlRecognitionModes(aidlPhrase.recognitionModes);
+ for (int aidlUser : aidlPhrase.users) {
+ hidlPhrase.users.add(aidlUser);
+ }
+ hidlPhrase.locale = aidlPhrase.locale;
+ hidlPhrase.text = aidlPhrase.text;
+ return hidlPhrase;
+ }
+
+ static int aidl2hidlRecognitionModes(int aidlModes) {
+ int hidlModes = 0;
+
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ }
+ return hidlModes;
+ }
+
+ static int hidl2aidlRecognitionModes(int hidlModes) {
+ int aidlModes = 0;
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return aidlModes;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.SoundModel aidl2hidlSoundModel(@NonNull SoundModel aidlModel) {
+ ISoundTriggerHw.SoundModel hidlModel = new ISoundTriggerHw.SoundModel();
+ hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
+ hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
+ hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
+ hidlModel.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlModel.data,
+ "SoundTrigger SoundModel");
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.PhraseSoundModel aidl2hidlPhraseSoundModel(
+ @NonNull PhraseSoundModel aidlModel) {
+ ISoundTriggerHw.PhraseSoundModel hidlModel = new ISoundTriggerHw.PhraseSoundModel();
+ hidlModel.common = aidl2hidlSoundModel(aidlModel.common);
+ for (Phrase aidlPhrase : aidlModel.phrases) {
+ hidlModel.phrases.add(aidl2hidlPhrase(aidlPhrase));
+ }
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ @NonNull RecognitionConfig aidlConfig) {
+ ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
+ hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
+ hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ }
+ hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ "SoundTrigger RecognitionConfig");
+ return hidlConfig;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra aidl2hidlPhraseRecognitionExtra(
+ @NonNull PhraseRecognitionExtra aidlExtra) {
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ hidlExtra.id = aidlExtra.id;
+ hidlExtra.recognitionModes = aidl2hidlRecognitionModes(aidlExtra.recognitionModes);
+ hidlExtra.confidenceLevel = aidlExtra.confidenceLevel;
+ hidlExtra.levels.ensureCapacity(aidlExtra.levels.length);
+ for (ConfidenceLevel aidlLevel : aidlExtra.levels) {
+ hidlExtra.levels.add(aidl2hidlConfidenceLevel(aidlLevel));
+ }
+ return hidlExtra;
+ }
+
+ static @NonNull
+ PhraseRecognitionExtra hidl2aidlPhraseRecognitionExtra(
+ @NonNull android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = hidlExtra.id;
+ aidlExtra.recognitionModes = hidl2aidlRecognitionModes(hidlExtra.recognitionModes);
+ aidlExtra.confidenceLevel = hidlExtra.confidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[hidlExtra.levels.size()];
+ for (int i = 0; i < hidlExtra.levels.size(); ++i) {
+ aidlExtra.levels[i] = hidl2aidlConfidenceLevel(hidlExtra.levels.get(i));
+ }
+ return aidlExtra;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel aidl2hidlConfidenceLevel(
+ @NonNull ConfidenceLevel aidlLevel) {
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ hidlLevel.userId = aidlLevel.userId;
+ hidlLevel.levelPercent = aidlLevel.levelPercent;
+ return hidlLevel;
+ }
+
+ static @NonNull
+ ConfidenceLevel hidl2aidlConfidenceLevel(
+ @NonNull android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.userId = hidlLevel.userId;
+ aidlLevel.levelPercent = hidlLevel.levelPercent;
+ return aidlLevel;
+ }
+
+ static int hidl2aidlRecognitionStatus(int hidlStatus) {
+ switch (hidlStatus) {
+ case ISoundTriggerHwCallback.RecognitionStatus.SUCCESS:
+ return RecognitionStatus.SUCCESS;
+ case ISoundTriggerHwCallback.RecognitionStatus.ABORT:
+ return RecognitionStatus.ABORTED;
+ case ISoundTriggerHwCallback.RecognitionStatus.FAILURE:
+ return RecognitionStatus.FAILURE;
+ case 3: // This doesn't have a constant in HIDL.
+ return RecognitionStatus.FORCED;
+ default:
+ throw new IllegalArgumentException("Unknown recognition status: " + hidlStatus);
+ }
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = new RecognitionEvent();
+ aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
+ aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
+ aidlEvent.captureAvailable = hidlEvent.captureAvailable;
+ // hidlEvent.captureSession is never a valid field.
+ aidlEvent.captureSession = -1;
+ aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
+ aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
+ aidlEvent.triggerInData = hidlEvent.triggerInData;
+ aidlEvent.audioConfig = hidl2aidlAudioConfig(hidlEvent.audioConfig);
+ aidlEvent.data = new byte[hidlEvent.data.size()];
+ for (int i = 0; i < aidlEvent.data.length; ++i) {
+ aidlEvent.data[i] = hidlEvent.data.get(i);
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = hidl2aidlRecognitionEvent(hidlEvent.header);
+ // Data needs to get overridden with 2.1 data.
+ aidlEvent.data = HidlMemoryUtil.hidlMemoryToByteArray(hidlEvent.data);
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ AudioConfig hidl2aidlAudioConfig(
+ @NonNull android.hardware.audio.common.V2_0.AudioConfig hidlConfig) {
+ AudioConfig aidlConfig = new AudioConfig();
+ // TODO(ytai): channelMask and format might need a more careful conversion to make sure the
+ // constants match.
+ aidlConfig.sampleRateHz = hidlConfig.sampleRateHz;
+ aidlConfig.channelMask = hidlConfig.channelMask;
+ aidlConfig.format = hidlConfig.format;
+ aidlConfig.offloadInfo = hidl2aidlOffloadInfo(hidlConfig.offloadInfo);
+ aidlConfig.frameCount = hidlConfig.frameCount;
+ return aidlConfig;
+ }
+
+ static @NonNull
+ AudioOffloadInfo hidl2aidlOffloadInfo(
+ @NonNull android.hardware.audio.common.V2_0.AudioOffloadInfo hidlInfo) {
+ AudioOffloadInfo aidlInfo = new AudioOffloadInfo();
+ // TODO(ytai): channelMask, format, streamType and usage might need a more careful
+ // conversion to make sure the constants match.
+ aidlInfo.sampleRateHz = hidlInfo.sampleRateHz;
+ aidlInfo.channelMask = hidlInfo.channelMask;
+ aidlInfo.format = hidlInfo.format;
+ aidlInfo.streamType = hidlInfo.streamType;
+ aidlInfo.bitRatePerSecond = hidlInfo.bitRatePerSecond;
+ aidlInfo.durationMicroseconds = hidlInfo.durationMicroseconds;
+ aidlInfo.hasVideo = hidlInfo.hasVideo;
+ aidlInfo.isStreaming = hidlInfo.isStreaming;
+ aidlInfo.bitWidth = hidlInfo.bitWidth;
+ aidlInfo.bufferSize = hidlInfo.bufferSize;
+ aidlInfo.usage = hidlInfo.usage;
+ return aidlInfo;
+ }
+
+ @Nullable
+ static ModelParameterRange hidl2aidlModelParameterRange(
+ android.hardware.soundtrigger.V2_3.ModelParameterRange hidlRange) {
+ if (hidlRange == null) {
+ return null;
+ }
+ ModelParameterRange aidlRange = new ModelParameterRange();
+ aidlRange.minInclusive = hidlRange.start;
+ aidlRange.maxInclusive = hidlRange.end;
+ return aidlRange;
+ }
+
+ static int aidl2hidlModelParameter(int aidlParam) {
+ switch (aidlParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR;
+
+ default:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalException.java b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
new file mode 100644
index 000000000000..8b3e70875183
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a non-zero status code returned by a HAL invocation.
+ * Depending on the operation that threw the error, the integrity of the HAL implementation and the
+ * client's tolerance to error, this error may or may not be recoverable. The HAL itself is expected
+ * to retain the state it had prior to the invocation (so, unless the error is a result of a HAL
+ * bug, normal operation may resume).
+ * <p>
+ * The reason why this is a RuntimeException, even though the HAL interface allows returning them
+ * is because we expect none of them to actually occur as part of correct usage of the HAL.
+ *
+ * @hide
+ */
+public class HalException extends RuntimeException {
+ public final int errorCode;
+
+ public HalException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public HalException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
new file mode 100644
index 000000000000..f0a0d8305bc6
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -0,0 +1,77 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.os.HidlMemoryUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Utilities for maintaining data compatibility between different minor versions of soundtrigger@2.x
+ * HAL.
+ * Note that some of these conversion utilities are destructive, i.e. mutate their input (for the
+ * sake of simplifying code and reducing copies).
+ */
+class Hw2CompatUtil {
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel convertSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 = soundModel.header;
+ // Note: this mutates the input!
+ model_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(soundModel.data);
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent convertRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ event_2_1.header = event;
+ event_2_1.data = HidlMemoryUtil.byteListToHidlMemory(event_2_1.header.data,
+ "SoundTrigger RecognitionEvent");
+ // Note: this mutates the input!
+ event_2_1.header.data = new ArrayList<>();
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent convertPhraseRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ event_2_1.common = convertRecognitionEvent_2_0_to_2_1(event.common);
+ event_2_1.phraseExtras = event.phraseExtras;
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel convertPhraseSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel();
+ model_2_0.common = convertSoundModel_2_1_to_2_0(soundModel.common);
+ model_2_0.phrases = soundModel.phrases;
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ config.header;
+ // Note: this mutates the input!
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ return config_2_0;
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
new file mode 100644
index 000000000000..81252c9a8c14
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -0,0 +1,158 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.ModelParameterRange;
+import android.hidl.base.V1_0.IBase;
+import android.os.IHwBinder;
+
+/**
+ * This interface mimics android.hardware.soundtrigger.V2_x.ISoundTriggerHw and
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHwCallback, with a few key differences:
+ * <ul>
+ * <li>Methods in the original interface generally have a status return value and potentially a
+ * second return value which is the actual return value. This is reflected via a synchronous
+ * callback, which is not very pleasant to work with. This interface replaces that pattern with
+ * the convention that a HalException is thrown for non-OK status, and then we can use the
+ * return value for the actual return value.
+ * <li>This interface will always include all the methods from the latest 2.x version (and thus
+ * from every 2.x version) interface, with the convention that unsupported methods throw a
+ * {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * <li>Cases where the original interface had multiple versions of a method representing the exact
+ * thing, or there exists a trivial conversion between the new and old version, this interface
+ * represents only the latest version, without any _version suffixes.
+ * <li>Removes some of the obscure IBinder methods.
+ * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
+ * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
+ * them.
+ * <li>soundModelCallback has been removed, since nobody cares about it. Implementations are free
+ * to silently discard it.
+ * </ul>
+ * For cases where the client wants to explicitly handle specific versions of the underlying driver
+ * interface, they may call {@link #interfaceDescriptor()}.
+ * <p>
+ * <b>Note to maintainers</b>: This class must always be kept in sync with the latest 2.x version,
+ * so that clients have access to the entire functionality without having to burden themselves with
+ * compatibility, as much as possible.
+ */
+public interface ISoundTriggerHw2 {
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+ */
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback)
+ */
+ int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadPhraseSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback)
+ */
+ int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#unloadSoundModel(int)
+ */
+ void unloadSoundModel(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopRecognition(int)
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopAllRecognitions()
+ */
+ void stopAllRecognitions();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
+ */
+ void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getModelState(int)
+ */
+ void getModelState(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getParameter(int, int,
+ * ISoundTriggerHw.getParameterCallback)
+ */
+ int getModelParameter(int modelHandle, int param);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#setParameter(int, int, int)
+ */
+ void setModelParameter(int modelHandle, int param, int value);
+
+ /**
+ * @return null if not supported.
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#queryParameter(int, int,
+ * ISoundTriggerHw.queryParameterCallback)
+ */
+ ModelParameterRange queryParameter(int modelHandle, int param);
+
+ /**
+ * @see IHwBinder#linkToDeath(IHwBinder.DeathRecipient, long)
+ */
+ boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie);
+
+ /**
+ * @see IHwBinder#unlinkToDeath(IHwBinder.DeathRecipient)
+ */
+ boolean unlinkToDeath(IHwBinder.DeathRecipient recipient);
+
+ /**
+ * @see IBase#interfaceDescriptor()
+ */
+ String interfaceDescriptor() throws android.os.RemoteException;
+
+ interface Callback {
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#recognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent,
+ * int)
+ */
+ void recognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#phraseRecognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent,
+ * int)
+ */
+ void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
new file mode 100644
index 000000000000..e1fb2266b7c6
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * An internal server error.
+ * <p>
+ * This exception wraps any exception thrown from a service implementation, which is a result of a
+ * bug in the server implementation (or any of its dependencies).
+ * <p>
+ * Specifically, this type is excluded from the set of whitelisted exceptions that binder would
+ * tunnel to the client process, since these exceptions are ambiguous regarding whether the client
+ * had done something wrong or the server is buggy. For example, a client getting an
+ * IllegalArgumentException cannot easily determine whether they had provided illegal arguments to
+ * the method they were calling, or whether the method implementation provided illegal arguments to
+ * some method it was calling due to a bug.
+ *
+ * @hide
+ */
+public class InternalServerError extends RuntimeException {
+ public InternalServerError(@NonNull Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
new file mode 100644
index 000000000000..83618505814e
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a fault which:
+ * <ul>
+ * <li>Could not have been anticipated by a caller (i.e. is not a violation of any preconditions).
+ * <li>Is guaranteed to not have been caused any meaningful state change in the callee. The caller
+ * may continue operation as if the call has never been made.
+ * </ul>
+ * <p>
+ * Some recoverable faults are permanent and some are transient / circumstantial, the specific error
+ * code can provide more information about the possible recovery options.
+ * <p>
+ * The reason why this is a RuntimeException is to allow it to go through interfaces defined by
+ * AIDL, which we have no control over.
+ *
+ * @hide
+ */
+public class RecoverableException extends RuntimeException {
+ public final int errorCode;
+
+ public RecoverableException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public RecoverableException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
new file mode 100644
index 000000000000..4a852c4b68e8
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -0,0 +1,470 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An implementation of {@link ISoundTriggerHw2}, on top of any
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of
+ * the details involved with retaining backward compatibility and adapts to the more pleasant syntax
+ * exposed by {@link ISoundTriggerHw2}, compared to the bare driver interface.
+ * <p>
+ * Exception handling:
+ * <ul>
+ * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}.
+ * <li>All HAL malfunctions get thrown as {@link HalException}.
+ * <li>All unsupported operations get thrown as {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * </ul>
+ */
+final class SoundTriggerHw2Compat implements ISoundTriggerHw2 {
+ private final @NonNull
+ IHwBinder mBinder;
+ private final @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+
+ public SoundTriggerHw2Compat(
+ @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw underlying) {
+ this(underlying.asBinder());
+ }
+
+ public SoundTriggerHw2Compat(IHwBinder binder) {
+ Objects.requireNonNull(binder);
+
+ mBinder = binder;
+
+ // We want to share the proxy instances rather than create a separate proxy for every
+ // version, so we go down the versions in descending order to find the latest one supported,
+ // and then simply up-cast it to obtain all the versions that are earlier.
+
+ // Attempt 2.3
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 =
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder);
+ if (as2_3 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3;
+ return;
+ }
+
+ // Attempt 2.2
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2 =
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder);
+ if (as2_2 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2;
+ mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.1
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1 =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder);
+ if (as2_1 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = as2_1;
+ mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.0
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0 =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder);
+ if (as2_0 != null) {
+ mUnderlying_2_0 = as2_0;
+ mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ throw new RuntimeException("Binder doesn't support ISoundTriggerHw@2.0");
+ }
+
+ private static void handleHalStatus(int status, String methodName) {
+ if (status != 0) {
+ throw new HalException(status, methodName);
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+ properties =
+ new AtomicReference<>();
+ as2_0().getProperties(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ handleHalStatus(retval.get(), "getProperties");
+ return properties.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadSoundModel_2_1(soundModel, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadPhraseSoundModel_2_1(soundModel, new SoundTriggerCallback(callback),
+ cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadPhraseSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void unloadSoundModel(int modelHandle) {
+ try {
+ int retval = as2_0().unloadSoundModel(modelHandle);
+ handleHalStatus(retval, "unloadSoundModel");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ try {
+ int retval = as2_0().stopRecognition(modelHandle);
+ handleHalStatus(retval, "stopRecognition");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
+ }
+
+ @Override
+ public void stopAllRecognitions() {
+ try {
+ int retval = as2_0().stopAllRecognitions();
+ handleHalStatus(retval, "stopAllRecognitions");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ Callback callback, int cookie) {
+ try {
+ try {
+ int retval = as2_1().startRecognition_2_1(modelHandle, config,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition_2_1");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void getModelState(int modelHandle) {
+ try {
+ int retval = as2_2().getModelState(modelHandle);
+ handleHalStatus(retval, "getModelState");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicInteger value = new AtomicInteger(0);
+ try {
+ as2_3().getParameter(modelHandle, param,
+ (s, v) -> {
+ status.set(s);
+ value.set(v);
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ handleHalStatus(status.get(), "getParameter");
+ return value.get();
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int param, int value) {
+ try {
+ int retval = as2_3().setParameter(modelHandle, param, value);
+ handleHalStatus(retval, "setParameter");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_3.ModelParameterRange queryParameter(int modelHandle,
+ int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange>
+ optionalRange =
+ new AtomicReference<>();
+ try {
+ as2_3().queryParameter(modelHandle, param,
+ (s, r) -> {
+ status.set(s);
+ optionalRange.set(r);
+ });
+ } catch (NotSupported e) {
+ // For older drivers, we consider no model parameter to be supported.
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ handleHalStatus(status.get(), "queryParameter");
+ return (optionalRange.get().getDiscriminator()
+ == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range)
+ ?
+ optionalRange.get().range() : null;
+ }
+
+ @Override
+ public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+ return mBinder.linkToDeath(recipient, cookie);
+ }
+
+ @Override
+ public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+ return mBinder.unlinkToDeath(recipient);
+ }
+
+ @Override
+ public String interfaceDescriptor() throws RemoteException {
+ return as2_0().interfaceDescriptor();
+ }
+
+ private int loadSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 =
+ Hw2CompatUtil.convertSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie, (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private int loadPhraseSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ Hw2CompatUtil.convertPhraseSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadPhraseSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private void startRecognition_2_0(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ Callback callback, int cookie)
+ throws RemoteException {
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+ int retval = as2_0().startRecognition(modelHandle, config_2_0,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition");
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0() {
+ return mUnderlying_2_0;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1() throws NotSupported {
+ if (mUnderlying_2_1 == null) {
+ throw new NotSupported("Underlying driver version < 2.1");
+ }
+ return mUnderlying_2_1;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2() throws NotSupported {
+ if (mUnderlying_2_2 == null) {
+ throw new NotSupported("Underlying driver version < 2.2");
+ }
+ return mUnderlying_2_2;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3() throws NotSupported {
+ if (mUnderlying_2_3 == null) {
+ throw new NotSupported("Underlying driver version < 2.3");
+ }
+ return mUnderlying_2_3;
+ }
+
+ /**
+ * A checked exception representing the requested interface version not being supported.
+ * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to
+ * the caller if the request cannot be fulfilled.
+ */
+ private static class NotSupported extends Exception {
+ NotSupported(String message) {
+ super(message);
+ }
+
+ /**
+ * Throw this as a recoverable exception.
+ *
+ * @return Never actually returns anything. Always throws. Used so that caller can write
+ * throw e.throwAsRecoverableException().
+ */
+ RecoverableException throwAsRecoverableException() {
+ throw new RecoverableException(Status.OPERATION_NOT_SUPPORTED, getMessage());
+ }
+ }
+
+ private static class SoundTriggerCallback extends
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub {
+ private final @NonNull
+ Callback mDelegate;
+
+ private SoundTriggerCallback(
+ @NonNull Callback delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public void recognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ mDelegate.recognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ mDelegate.phraseRecognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void soundModelCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+
+ @Override
+ public void recognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.recognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.phraseRecognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void soundModelCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
new file mode 100644
index 000000000000..9d51b65ea152
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -0,0 +1,140 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an implementation of the ISoundTriggerMiddlewareService interface.
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle invalid
+ * usage, and such usage will result in undefined behavior. If this service is to be offered to an
+ * untrusted client, it must be wrapped with input and state validation.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status}
+ * constants. Any other exception thrown should be regarded as a bug in the implementation or one
+ * of its dependencies (assuming correct usage).
+ * <li>The implementation is designed for testibility by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies on
+ * Android runtime.
+ * <li>The implementation is thread-safe.
+ * </ul>
+ *
+ * @hide
+ */
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+ static private final String TAG = "SoundTriggerMiddlewareImpl";
+ private final SoundTriggerModule[] mModules;
+
+ /**
+ * Interface to the audio system, which can allocate capture session handles.
+ * SoundTrigger uses those sessions in order to associate a recognition session with an optional
+ * capture from the same device that triggered the recognition.
+ */
+ public static abstract class AudioSessionProvider {
+ public static final class AudioSession {
+ final int mSessionHandle;
+ final int mIoHandle;
+ final int mDeviceHandle;
+
+ AudioSession(int sessionHandle, int ioHandle, int deviceHandle) {
+ mSessionHandle = sessionHandle;
+ mIoHandle = ioHandle;
+ mDeviceHandle = deviceHandle;
+ }
+ }
+
+ public abstract AudioSession acquireSession();
+
+ public abstract void releaseSession(int sessionHandle);
+ }
+
+ /**
+ * Most generic constructor - gets an array of HAL driver instances.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+
+ for (int i = 0; i < halServices.length; ++i) {
+ ISoundTriggerHw service = halServices[i];
+ try {
+ modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ }
+ }
+
+ mModules = modules.toArray(new SoundTriggerModule[modules.size()]);
+ }
+
+ /**
+ * Convenience constructor - gets a single HAL driver instance.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ SoundTriggerModuleDescriptor[] result = new SoundTriggerModuleDescriptor[mModules.length];
+
+ for (int i = 0; i < mModules.length; ++i) {
+ SoundTriggerModuleDescriptor desc = new SoundTriggerModuleDescriptor();
+ desc.handle = i;
+ desc.properties = mModules[i].getProperties();
+ result[i] = desc;
+ }
+ return result;
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ return mModules[handle].attach(callback);
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ for (SoundTriggerModule module : mModules) {
+ module.setExternalCaptureState(active);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
new file mode 100644
index 000000000000..a7cfe1037f11
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -0,0 +1,709 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
+ * it as a Binder service and enforces permissions and correct usage by the client, as well as makes
+ * sure that exceptions representing a server malfunction do not get sent to the client.
+ * <p>
+ * This is intended to extract the non-business logic out of the underlying implementation and thus
+ * make it easier to maintain each one of those separate aspects. A design trade-off is being made
+ * here, in that this class would need to essentially eavesdrop on all the client-server
+ * communication and retain all state known to the client, while the client doesn't necessarily care
+ * about all of it, and while the server has its own representation of this information. However,
+ * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
+ * There is also some additional cost in employing a simplistic locking mechanism here, but
+ * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow the following
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ * // Permission check.
+ * checkPermissions();
+ * // Input validation.
+ * ValidationUtil.validateS(arg);
+ * synchronized (this) {
+ * // State validation.
+ * if (...state is not valid for this call...) {
+ * throw new IllegalStateException("State is invalid because...");
+ * }
+ * // From here on, every exception isn't client's fault.
+ * try {
+ * T result = mDelegate.method(arg);
+ * // Update state.;
+ * ...
+ * return result;
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * }
+ * }
+ * </pre></code>
+ * Following this patterns ensures a consistent and rigorous handling of all aspects associated
+ * with client-server separation.
+ * <p>
+ * <b>Exception handling approach:</b><br>
+ * We make sure all client faults (permissions, argument and state validation) happen first, and
+ * would throw {@link SecurityException}, {@link IllegalArgumentException}/
+ * {@link NullPointerException} or {@link
+ * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
+ * will get sent back to the client.<br>
+ * Once this is done, any subsequent fault is considered a server fault. Only {@link
+ * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
+ * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
+ * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
+ * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
+ * exception handler on the server side, typically resulting in rebooting the server.
+ * <p>
+ * <b>Exposing this service as a System Service:</b><br>
+ * Insert this line into {@link com.android.server.SystemServer}:
+ * <code><pre>
+ * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ * </pre></code>
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
+ static private final String TAG = "SoundTriggerMiddlewareService";
+
+ final ISoundTriggerMiddlewareService mDelegate;
+ final Context mContext;
+ Set<Integer> mModuleHandles;
+
+ /**
+ * Constructor for internal use only. Could be exposed for testing purposes in the future.
+ * Users should access this class via {@link Lifecycle}.
+ */
+ private SoundTriggerMiddlewareService(
+ @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+ mDelegate = delegate;
+ mContext = context;
+ }
+
+ /**
+ * Generic exception handling for exceptions thrown by the underlying implementation.
+ *
+ * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
+ * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
+ * (<b>not</b> passed by Binder to the caller).
+ * <p>
+ * Typical usage:
+ * <code><pre>
+ * try {
+ * ... Do server operations ...
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * </pre></code>
+ */
+ private static @NonNull
+ RuntimeException handleException(@NonNull Exception e) {
+ if (e instanceof RecoverableException) {
+ throw new ServiceSpecificException(((RecoverableException) e).errorCode,
+ e.getMessage());
+ }
+ throw new InternalServerError(e);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+ mModuleHandles = new HashSet<>(result.length);
+ for (SoundTriggerModuleDescriptor desc : result) {
+ mModuleHandles.add(desc.handle);
+ }
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(callback.asBinder());
+
+ synchronized (this) {
+ // State validation.
+ if (mModuleHandles == null) {
+ throw new IllegalStateException(
+ "Client must call listModules() prior to attaching.");
+ }
+ if (!mModuleHandles.contains(handle)) {
+ throw new IllegalArgumentException("Invalid handle: " + handle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModuleService moduleService = new ModuleService(callback);
+ moduleService.attach(mDelegate.attach(handle, moduleService));
+ return moduleService;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ // Permission check.
+ checkPreemptPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setExternalCaptureState(active);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
+ * service.
+ */
+ private void checkPermissions() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
+ "Caller must have the android.permission.RECORD_AUDIO permission.");
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
+ "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
+ * active sound trigger sessions.
+ */
+ private void checkPreemptPermissions() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
+ "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+ }
+
+ /** State of a sound model. */
+ static class ModelState {
+ /** Activity state of a sound model. */
+ enum Activity {
+ /** Model is loaded, recognition is inactive. */
+ LOADED,
+ /** Model is loaded, recognition is active. */
+ ACTIVE
+ }
+
+ /** Activity state. */
+ public Activity activityState = Activity.LOADED;
+
+ /**
+ * A map of known parameter support. A missing key means we don't know yet whether the
+ * parameter is supported. A null value means it is known to not be supported. A non-null
+ * value indicates the valid value range.
+ */
+ private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+
+ /**
+ * Check that the given parameter is known to be supported for this model.
+ *
+ * @param modelParam The parameter key.
+ */
+ public void checkSupported(int modelParam) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ }
+
+ /**
+ * Check that the given parameter is known to be supported for this model and that the given
+ * value is a valid value for it.
+ *
+ * @param modelParam The parameter key.
+ * @param value The value.
+ */
+ public void checkSupported(int modelParam, int value) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
+ "value");
+ }
+
+ /**
+ * Update support state for the given parameter for this model.
+ *
+ * @param modelParam The parameter key.
+ * @param range The parameter value range, or null if not supported.
+ */
+ public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ parameterSupport.put(modelParam, range);
+ }
+ }
+
+ /**
+ * Entry-point to this module: exposes the module as a {@link SystemService}.
+ */
+ public static final class Lifecycle extends SystemService {
+ private SoundTriggerMiddlewareService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ ISoundTriggerHw[] services;
+ try {
+ services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
+ Log.d(TAG, "Connected to default ISoundTriggerHw");
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
+ services = new ISoundTriggerHw[0];
+ }
+
+ mService = new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
+ getContext());
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ }
+ }
+
+ /**
+ * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+ * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+ */
+ private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
+ DeathRecipient {
+ private final ISoundTriggerCallback mCallback;
+ private ISoundTriggerModule mDelegate;
+ private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+
+ ModuleService(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateGenericModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validatePhraseModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadPhraseModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for unloading: " + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.unloadModel(modelHandle);
+ mLoadedModels.remove(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateRecognitionConfig(config);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for starting recognition: "
+ + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.startRecognition(modelHandle, config);
+ modelState.activityState = ModelState.Activity.ACTIVE;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // stopRecognition is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.stopRecognition(modelHandle);
+ modelState.activityState = ModelState.Activity.LOADED;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // forceRecognitionEvent is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam, value);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ modelState.updateParameterSupport(modelParam, result);
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void detach() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has already been detached.");
+ }
+ if (!mLoadedModels.isEmpty()) {
+ throw new IllegalStateException("Cannot detach while models are loaded.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ private void detachInternal() {
+ try {
+ mDelegate.detach();
+ mDelegate = null;
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Callbacks
+
+ @Override
+ public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
+ synchronized (this) {
+ if (event.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
+ synchronized (this) {
+ if (event.common.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ synchronized (this) {
+ try {
+ mCallback.onRecognitionAvailabilityChange(available);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ // This is called whenever our client process dies.
+ synchronized (this) {
+ try {
+ // Gracefully stop all active recognitions and unload the models.
+ for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+ if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(entry.getKey());
+ }
+ mDelegate.unloadModel(entry.getKey());
+ }
+ // Detach.
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
new file mode 100644
index 000000000000..81789e1362c0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -0,0 +1,545 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is an implementation of a single module of the ISoundTriggerMiddlewareService interface,
+ * exposing itself through the {@link ISoundTriggerModule} interface, possibly to multiple separate
+ * clients.
+ * <p>
+ * Typical usage is to query the module capabilities using {@link #getProperties()} and then to use
+ * the module through an {@link ISoundTriggerModule} instance, obtained via {@link
+ * #attach(ISoundTriggerCallback)}. Every such interface is its own session and state is not shared
+ * between sessions (i.e. cannot use a handle obtained from one session through another).
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle
+ * invalid usage, and such usage will result in undefined behavior. If this service is to be
+ * offered to an untrusted client, it must be wrapped with input and state validation.
+ * <li>The underlying driver is assumed to be correct. This implementation does not attempt to
+ * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
+ * service is to used with an untrusted driver, the driver must be wrapped with validation / error
+ * recovery code.
+ * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
+ * considered recoverable faults and should not occur in a properly functioning system.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
+ * thrown should be regarded as a bug in the implementation or one of its dependencies
+ * (assuming correct usage).
+ * <li>The implementation is designed for testability by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
+ * on Android runtime.
+ * <li>The implementation is thread-safe. This is achieved by a simplistic model, where all entry-
+ * points (both client API and driver callbacks) obtain a lock on the SoundTriggerModule instance
+ * for their entire scope. Any other method can be assumed to be running with the lock already
+ * obtained, so no further locking should be done. While this is not necessarily the most efficient
+ * synchronization strategy, it is very easy to reason about and this code is likely not on any
+ * performance-critical
+ * path.
+ * </ul>
+ *
+ * @hide
+ */
+class SoundTriggerModule {
+ static private final String TAG = "SoundTriggerModule";
+ @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
+ private final Set<Session> mActiveSessions = new HashSet<>();
+ private int mNumLoadedModels = 0;
+ private SoundTriggerModuleProperties mProperties = null;
+ private boolean mRecognitionAvailable;
+
+ /**
+ * Ctor.
+ *
+ * @param halService The underlying HAL driver.
+ */
+ SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
+ assert halService != null;
+ mHalService = new SoundTriggerHw2Compat(halService);
+ mAudioSessionProvider = audioSessionProvider;
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+
+ // We conservatively assume that external capture is active until explicitly told otherwise.
+ mRecognitionAvailable = mProperties.concurrentCapture;
+ }
+
+ /**
+ * Establish a client session with this module.
+ *
+ * This module may be shared by multiple clients, each will get its own session. While resources
+ * are shared between the clients, each session has its own state and data should not be shared
+ * across sessions.
+ *
+ * @param callback The client callback, which will be used for all messages. This is a oneway
+ * callback, so will never block, throw an unchecked exception or return a
+ * value.
+ * @return The interface through which this module can be controlled.
+ */
+ synchronized @NonNull
+ Session attach(@NonNull ISoundTriggerCallback callback) {
+ Log.d(TAG, "attach()");
+ Session session = new Session(callback);
+ mActiveSessions.add(session);
+ return session;
+ }
+
+ /**
+ * Query the module's properties.
+ *
+ * @return The properties structure.
+ */
+ synchronized @NonNull
+ SoundTriggerModuleProperties getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Notify the module that external capture has started / finished, using the same input device
+ * used for recognition.
+ * If the underlying driver does not support recognition while capturing, capture will be
+ * aborted, and the recognition callback will receive and abort event. In addition, all active
+ * clients will be notified of the change in state.
+ *
+ * @param active true iff external capture is active.
+ */
+ synchronized void setExternalCaptureState(boolean active) {
+ Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+ if (mProperties.concurrentCapture) {
+ // If we support concurrent capture, we don't care about any of this.
+ return;
+ }
+ mRecognitionAvailable = !active;
+ if (!mRecognitionAvailable) {
+ // Our module does not support recognition while a capture is active -
+ // need to abort all active recognitions.
+ for (Session session : mActiveSessions) {
+ session.abortActiveRecognitions();
+ }
+ }
+ for (Session session : mActiveSessions) {
+ session.notifyRecognitionAvailability();
+ }
+ }
+
+ /**
+ * Remove session from the list of active sessions.
+ *
+ * @param session The session to remove.
+ */
+ private void removeSession(@NonNull Session session) {
+ mActiveSessions.remove(session);
+ }
+
+ /** State of a single sound model. */
+ private enum ModelState {
+ /** Initial state, until load() is called. */
+ INIT,
+ /** Model is loaded, but recognition is not active. */
+ LOADED,
+ /** Model is loaded and recognition is active. */
+ ACTIVE
+ }
+
+ /**
+ * A single client session with this module.
+ *
+ * This is the main interface used to interact with this module.
+ */
+ private class Session implements ISoundTriggerModule {
+ private ISoundTriggerCallback mCallback;
+ private Map<Integer, Model> mLoadedModels = new HashMap<>();
+
+ /**
+ * Ctor.
+ *
+ * @param callback The client callback interface.
+ */
+ private Session(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ notifyRecognitionAvailability();
+ }
+
+ @Override
+ public void detach() {
+ Log.d(TAG, "detach()");
+ synchronized (SoundTriggerModule.this) {
+ removeSession(this);
+ }
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ Log.d(TAG, String.format("loadModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ return result;
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+ return result;
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).unload();
+ --mNumLoadedModels;
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ Log.d(TAG,
+ String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).startRecognition(config);
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).stopRecognition();
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).forceRecognitionEvent();
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ Log.d(TAG,
+ String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
+ modelParam, value));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).setParameter(modelParam, value);
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).getParameter(modelParam);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
+ }
+ }
+
+ /**
+ * Abort all currently active recognitions.
+ */
+ private void abortActiveRecognitions() {
+ for (Model model : mLoadedModels.values()) {
+ model.abortActiveRecognition();
+ }
+ }
+
+ private void notifyRecognitionAvailability() {
+ try {
+ mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not intended to be used directly with Binder.");
+ }
+
+ /**
+ * A single sound model in the system.
+ *
+ * All model-based operations are delegated to this class and implemented here.
+ */
+ private class Model implements ISoundTriggerHw2.Callback {
+ public int mHandle;
+ private ModelState mState = ModelState.INIT;
+ private int mModelType = SoundModelType.UNKNOWN;
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
+
+ private @NonNull
+ ModelState getState() {
+ return mState;
+ }
+
+ private void setState(@NonNull ModelState state) {
+ mState = state;
+ SoundTriggerModule.this.notifyAll();
+ }
+
+ private void waitStateChange() throws InterruptedException {
+ SoundTriggerModule.this.wait();
+ }
+
+ private int load(@NonNull SoundModel model) {
+ mModelType = model.type;
+ ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private int load(@NonNull PhraseSoundModel model) {
+ mModelType = model.common.type;
+ ISoundTriggerHw.PhraseSoundModel hidlModel =
+ ConversionUtil.aidl2hidlPhraseSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private void unload() {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ mHalService.unloadSoundModel(mHandle);
+ mLoadedModels.remove(mHandle);
+ }
+
+ private void startRecognition(@NonNull RecognitionConfig config) {
+ if (!mRecognitionAvailable) {
+ // Recognition is unavailable - send an abort event immediately.
+ notifyAbort();
+ return;
+ }
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ ConversionUtil.aidl2hidlRecognitionConfig(config);
+ hidlConfig.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.header.captureHandle = mSession.mIoHandle;
+ mHalService.startRecognition(mHandle, hidlConfig, this, 0);
+ setState(ModelState.ACTIVE);
+ }
+
+ private void stopRecognition() {
+ if (getState() == ModelState.LOADED) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.stopRecognition(mHandle);
+ setState(ModelState.LOADED);
+ }
+
+ /** Request a forced recognition event. Will do nothing if recognition is inactive. */
+ private void forceRecognitionEvent() {
+ if (getState() != ModelState.ACTIVE) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.getModelState(mHandle);
+ }
+
+
+ private void setParameter(int modelParam, int value) {
+ mHalService.setModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam), value);
+ }
+
+ private int getParameter(int modelParam) {
+ return mHalService.getModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam));
+ }
+
+ @Nullable
+ private ModelParameterRange queryModelParameterSupport(int modelParam) {
+ return ConversionUtil.hidl2aidlModelParameterRange(
+ mHalService.queryParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam)));
+ }
+
+ /** Abort the recognition, if active. */
+ private void abortActiveRecognition() {
+ // If we're inactive, do nothing.
+ if (getState() != ModelState.ACTIVE) {
+ return;
+ }
+ // Stop recognition.
+ stopRecognition();
+
+ // Notify the client that recognition has been aborted.
+ notifyAbort();
+ }
+
+ /** Notify the client that recognition has been aborted. */
+ private void notifyAbort() {
+ try {
+ switch (mModelType) {
+ case SoundModelType.GENERIC: {
+ android.media.soundtrigger_middleware.RecognitionEvent event =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onRecognition(mHandle, event);
+ }
+ break;
+
+ case SoundModelType.KEYPHRASE: {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
+ new android.media.soundtrigger_middleware.PhraseRecognitionEvent();
+ event.common =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.common.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onPhraseRecognition(mHandle, event);
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Unknown model type: " + mModelType);
+
+ }
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+
+ @Override
+ public void recognitionCallback(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
+ recognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+ aidlEvent.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ if (aidlEvent.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
+ phraseRecognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+ aidlEvent.common.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onPhraseRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ if (aidlEvent.common.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
new file mode 100644
index 000000000000..9ed894bc1ca9
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.soundtrigger_middleware"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
new file mode 100644
index 000000000000..80f69d08c089
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
@@ -0,0 +1,44 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for representing UUIDs as strings.
+ *
+ * @hide
+ */
+public class UuidUtil {
+ /**
+ * Regex pattern that can be used to validate / extract the various fields of a string-formatted
+ * UUID.
+ */
+ static final Pattern PATTERN = Pattern.compile("^([a-fA-F0-9]{8})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})$");
+
+ /** Printf-style pattern for formatting the various fields of a UUID as a string. */
+ static final String FORMAT = "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x";
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
new file mode 100644
index 000000000000..4898e6b59ab2
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -0,0 +1,117 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for asserting the validity of various data types used by this module.
+ * Each of the methods below would throw an {@link IllegalArgumentException} if its input is
+ * invalid. The input's validity is determined irrespective of any context. In cases where the valid
+ * value space is further limited by state, it is the caller's responsibility to assert.
+ *
+ * @hide
+ */
+public class ValidationUtil {
+ static void validateUuid(@Nullable String uuid) {
+ Preconditions.checkNotNull(uuid);
+ Matcher matcher = UuidUtil.PATTERN.matcher(uuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ "Illegal format for UUID: " + uuid);
+ }
+ }
+
+ static void validateGenericModel(@Nullable SoundModel model) {
+ validateModel(model, SoundModelType.GENERIC);
+ }
+
+ static void validateModel(@Nullable SoundModel model, int expectedType) {
+ Preconditions.checkNotNull(model);
+ if (model.type != expectedType) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ validateUuid(model.uuid);
+ validateUuid(model.vendorUuid);
+ Preconditions.checkNotNull(model.data);
+ }
+
+ static void validatePhraseModel(@Nullable PhraseSoundModel model) {
+ Preconditions.checkNotNull(model);
+ validateModel(model.common, SoundModelType.KEYPHRASE);
+ Preconditions.checkNotNull(model.phrases);
+ for (Phrase phrase : model.phrases) {
+ Preconditions.checkNotNull(phrase);
+ if ((phrase.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ Preconditions.checkNotNull(phrase.users);
+ Preconditions.checkNotNull(phrase.locale);
+ Preconditions.checkNotNull(phrase.text);
+ }
+ }
+
+ static void validateRecognitionConfig(@Nullable RecognitionConfig config) {
+ Preconditions.checkNotNull(config);
+ Preconditions.checkNotNull(config.phraseRecognitionExtras);
+ for (PhraseRecognitionExtra extra : config.phraseRecognitionExtras) {
+ Preconditions.checkNotNull(extra);
+ if ((extra.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ if (extra.confidenceLevel < 0 || extra.confidenceLevel > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ Preconditions.checkNotNull(extra.levels);
+ for (ConfidenceLevel level : extra.levels) {
+ Preconditions.checkNotNull(level);
+ if (level.levelPercent < 0 || level.levelPercent > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ }
+ }
+ Preconditions.checkNotNull(config.data);
+ }
+
+ static void validateModelParameter(int modelParam) {
+ switch (modelParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return;
+
+ default:
+ throw new IllegalArgumentException("Invalid model parameter");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 1b1ac6d3ed07..d99e03b091a3 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,6 +23,7 @@ import android.app.AlarmManager;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.Slog;
@@ -30,7 +31,6 @@ import android.util.TimestampedValue;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
@@ -512,12 +512,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
mLastAutoSystemClockTimeSet = null;
}
- // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
+ // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
// when setting the time using NITZ.
if (origin == ORIGIN_PHONE) {
// Send a broadcast that telephony code used to send after setting the clock.
// TODO Remove this broadcast as soon as there are no remaining listeners.
- Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time", newSystemClockMillis);
mCallback.sendStickyBroadcast(intent);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b4d80531be54..b3013c7e0a5f 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -385,7 +385,7 @@ public class TimeZoneDetectorStrategy {
}
private static boolean isOriginAutomatic(@Origin int origin) {
- return origin == ORIGIN_PHONE;
+ return origin != ORIGIN_MANUAL;
}
@GuardedBy("this")
@@ -456,15 +456,17 @@ public class TimeZoneDetectorStrategy {
* Dumps internal state such as field values.
*/
public synchronized void dumpState(PrintWriter pw, String[] args) {
- pw.println("TimeZoneDetectorStrategy:");
- pw.println("mCallback.isTimeZoneDetectionEnabled()="
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("TimeZoneDetectorStrategy:");
+
+ ipw.increaseIndent(); // level 1
+ ipw.println("mCallback.isTimeZoneDetectionEnabled()="
+ mCallback.isAutoTimeZoneDetectionEnabled());
- pw.println("mCallback.isDeviceTimeZoneInitialized()="
+ ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+ mCallback.isDeviceTimeZoneInitialized());
- pw.println("mCallback.getDeviceTimeZone()="
+ ipw.println("mCallback.getDeviceTimeZone()="
+ mCallback.getDeviceTimeZone());
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("Time zone change log:");
ipw.increaseIndent(); // level 2
mTimeZoneChangesLog.dump(ipw);
@@ -485,8 +487,6 @@ public class TimeZoneDetectorStrategy {
ipw.decreaseIndent(); // level 2
ipw.decreaseIndent(); // level 1
ipw.flush();
-
- pw.flush();
}
/**
diff --git a/services/core/java/com/android/server/utils/TEST_MAPPING b/services/core/java/com/android/server/utils/TEST_MAPPING
new file mode 100644
index 000000000000..bb7cea98eda0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.utils"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/utils/quota/Categorizer.java b/services/core/java/com/android/server/utils/quota/Categorizer.java
new file mode 100644
index 000000000000..bf2499114783
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Categorizer.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Identifies the {@link Category} that each UPTC belongs in.
+ *
+ * @see Uptc
+ */
+public interface Categorizer {
+ /**
+ * Return the {@link Category} that this UPTC belongs to.
+ *
+ * @see Uptc
+ */
+ @NonNull
+ Category getCategory(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/utils/quota/Category.java b/services/core/java/com/android/server/utils/quota/Category.java
new file mode 100644
index 000000000000..d8459d7d806a
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Category.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CategoryProto;
+
+/**
+ * A category as defined by the (system) client. Categories are used to put UPTCs in different
+ * groups. A sample group of Categories could be the various App Standby buckets or foreground vs
+ * background.
+ *
+ * @see Uptc
+ */
+public final class Category {
+ @NonNull
+ private final String mName;
+
+ private final int mHash;
+
+ /** Construct a new Category with the specified name. */
+ public Category(@NonNull String name) {
+ mName = name;
+ mHash = name.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other instanceof Category) {
+ return this.mName.equals(((Category) other).mName);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return mHash;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "Category{" + mName + "}";
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(CategoryProto.NAME, mName);
+ proto.end(token);
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
new file mode 100644
index 000000000000..b6730505f6f7
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Listener that is notified when a UPTC goes in and out of quota.
+ *
+ * @see Uptc
+ */
+public interface QuotaChangeListener {
+ /**
+ * Called when the UPTC reaches its quota or comes back into quota.
+ *
+ * @see Uptc
+ */
+ void onQuotaStateChanged(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/utils/quota/Uptc.java b/services/core/java/com/android/server/utils/quota/Uptc.java
new file mode 100644
index 000000000000..40775441ef67
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Uptc.java
@@ -0,0 +1,88 @@
+/*
+ * 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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.UptcProto;
+
+import java.util.Objects;
+
+/**
+ * A data object that represents a userId-packageName-tag combination (UPTC). The tag can be any
+ * desired String.
+ */
+final class Uptc {
+ public final int userId;
+ @NonNull
+ public final String packageName;
+ @Nullable
+ public final String tag;
+
+ private final int mHash;
+
+ /** Construct a new Uptc with the specified values. */
+ Uptc(int userId, @NonNull String packageName, @Nullable String tag) {
+ this.userId = userId;
+ this.packageName = packageName;
+ this.tag = tag;
+
+ mHash = 31 * userId
+ + 31 * packageName.hashCode()
+ + tag == null ? 0 : (31 * tag.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return string(userId, packageName, tag);
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(UptcProto.USER_ID, userId);
+ proto.write(UptcProto.NAME, packageName);
+ proto.write(UptcProto.TAG, tag);
+
+ proto.end(token);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Uptc) {
+ final Uptc other = (Uptc) obj;
+ return userId == other.userId
+ && Objects.equals(packageName, other.packageName)
+ && Objects.equals(tag, other.tag);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHash;
+ }
+
+ /** Standardize the output of a UPTC. */
+ static String string(int userId, @NonNull String packageName, @Nullable String tag) {
+ return "<" + userId + ">" + packageName + (tag == null ? "" : ("::" + tag));
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
new file mode 100644
index 000000000000..7b499139aa7c
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -0,0 +1,163 @@
+/*
+ * 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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.SparseArrayMap;
+
+import java.util.function.Consumer;
+
+/**
+ * A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
+ * (UPTC)->object associations. Tags are any desired String.
+ *
+ * @see Uptc
+ */
+class UptcMap<T> {
+ private final SparseArrayMap<ArrayMap<String, T>> mData = new SparseArrayMap<>();
+
+ public void add(int userId, @NonNull String packageName, @Nullable String tag,
+ @Nullable T obj) {
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data == null) {
+ data = new ArrayMap<>();
+ mData.add(userId, packageName, data);
+ }
+ data.put(tag, obj);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ public boolean contains(int userId, @NonNull String packageName) {
+ return mData.contains(userId, packageName);
+ }
+
+ public boolean contains(int userId, @NonNull String packageName, @Nullable String tag) {
+ // This structure never inserts a null ArrayMap, so if get(userId, packageName) returns
+ // null, the UPTC was never inserted.
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null && data.containsKey(tag);
+ }
+
+ /** Removes all the data for the user, if there was any. */
+ public void delete(int userId) {
+ mData.delete(userId);
+ }
+
+ /** Removes the data for the user, package, and tag, if there was any. */
+ public void delete(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data != null) {
+ data.remove(tag);
+ if (data.size() == 0) {
+ mData.delete(userId, packageName);
+ }
+ }
+ }
+
+ /** Removes the data for the user and package, if there was any. */
+ public ArrayMap<String, T> delete(int userId, @NonNull String packageName) {
+ return mData.delete(userId, packageName);
+ }
+
+ /**
+ * Returns the set of tag -> object mappings for the given userId and packageName
+ * combination.
+ */
+ @Nullable
+ public ArrayMap<String, T> get(int userId, @NonNull String packageName) {
+ return mData.get(userId, packageName);
+ }
+
+ /** Returns the saved object for the given UPTC. */
+ @Nullable
+ public T get(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null ? data.get(tag) : null;
+ }
+
+ /**
+ * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
+ * or a negative number if the specified userId is not mapped.
+ */
+ public int indexOfUserId(int userId) {
+ return mData.indexOfKey(userId);
+ }
+
+ /**
+ * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
+ * specified userId, or a negative number if the specified userId and packageName are not mapped
+ * together.
+ */
+ public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
+ return mData.indexOfKey(userId, packageName);
+ }
+
+ /** Returns the userId at the given index. */
+ public int getUserIdAtIndex(int index) {
+ return mData.keyAt(index);
+ }
+
+ /** Returns the package name at the given index. */
+ @NonNull
+ public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+ return mData.keyAt(userIndex, packageIndex);
+ }
+
+ /** Returns the tag at the given index. */
+ @NonNull
+ public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+ // This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
+ // won't return null.
+ return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
+ }
+
+ /** Returns the size of the outer (userId) array. */
+ public int userCount() {
+ return mData.numMaps();
+ }
+
+ /** Returns the number of packages saved for a given userId. */
+ public int packageCountForUser(int userId) {
+ return mData.numElementsForKey(userId);
+ }
+
+ /** Returns the number of tags saved for a given userId-packageName combination. */
+ public int tagCountForUserAndPackage(int userId, @NonNull String packageName) {
+ final ArrayMap data = mData.get(userId, packageName);
+ return data != null ? data.size() : 0;
+ }
+
+ /** Returns the value T at the given user, package, and tag indices. */
+ @Nullable
+ public T valueAt(int userIndex, int packageIndex, int tagIndex) {
+ final ArrayMap<String, T> data = mData.valueAt(userIndex, packageIndex);
+ return data != null ? data.valueAt(tagIndex) : null;
+ }
+
+ public void forEach(Consumer<T> consumer) {
+ mData.forEach((tagMap) -> {
+ for (int i = tagMap.size() - 1; i >= 0; --i) {
+ consumer.accept(tagMap.valueAt(i));
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 6f124ac5a54e..0d6e928cf710 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3986,6 +3986,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn
final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds,
PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(),
insetBounds, alignBottom);
+ forAllTasks(c);
c.recycle();
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e3ea2c5f1ac4..4667eab1f55b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4955,7 +4955,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// Re-parent IME's SurfaceControl when MagnificationSpec changed.
updateImeParent();
- applyMagnificationSpec(getPendingTransaction(), spec);
+ if (spec.scale != 1.0) {
+ applyMagnificationSpec(getPendingTransaction(), spec);
+ } else {
+ clearMagnificationSpec(getPendingTransaction());
+ }
getPendingTransaction().apply();
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 082187384646..184e7d61c355 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -24,6 +24,8 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.ViewRootImpl.sNewInsetsMode;
+import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
@@ -36,7 +38,6 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
@@ -222,8 +223,8 @@ class InsetsSourceProvider {
return;
}
mClientVisible = clientVisible;
- mDisplayContent.mWmService.mH.sendMessage(PooledLambda.obtainMessage(
- DisplayContent::layoutAndAssignWindowLayersIfNeeded, mDisplayContent));
+ mDisplayContent.mWmService.mH.obtainMessage(
+ LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
updateVisibility();
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index dee9e9f266a8..4de36b8b551e 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -89,6 +89,14 @@ class TaskSnapshotController {
@VisibleForTesting
static final int SNAPSHOT_MODE_NONE = 2;
+ /**
+ * Constant for <code>scaleFactor</code> when calling {@link #snapshotTask} which is
+ * interpreted as using the most appropriate scale ratio for the system.
+ * This may yield a smaller ratio on low memory devices.
+ */
+ @VisibleForTesting
+ static final float SNAPSHOT_SCALE_AUTO = -1f;
+
private final WindowManagerService mService;
private final TaskSnapshotCache mCache;
@@ -260,6 +268,84 @@ class TaskSnapshotController {
});
}
+ /**
+ * Validates the state of the Task is appropriate to capture a snapshot, collects
+ * information from the task and populates the builder.
+ *
+ * @param task the task to capture
+ * @param scaleFraction the scale fraction between 0-1.0, or {@link #SNAPSHOT_SCALE_AUTO}
+ * to automatically select
+ * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
+ * automatically select
+ * @param builder the snapshot builder to populate
+ *
+ * @return true if the state of the task is ok to proceed
+ */
+ private boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
+ TaskSnapshot.Builder builder) {
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return false;
+ }
+ final ActivityRecord activity = findAppTokenForSnapshot(task);
+ if (activity == null) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
+ }
+ return false;
+ }
+ if (activity.hasCommittedReparentToAnimationLeash()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
+ }
+ return false;
+ }
+
+ final WindowState mainWindow = activity.findMainWindow();
+ if (mainWindow == null) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
+ return false;
+ }
+
+ builder.setIsRealSnapshot(true);
+ builder.setId(System.currentTimeMillis());
+ builder.setContentInsets(getInsets(mainWindow));
+
+ final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+
+ if (scaleFraction == SNAPSHOT_SCALE_AUTO) {
+ builder.setScaleFraction(isLowRamDevice
+ ? mPersister.getReducedScale()
+ : mFullSnapshotScale);
+ builder.setReducedResolution(isLowRamDevice);
+ } else {
+ builder.setScaleFraction(scaleFraction);
+ builder.setReducedResolution(scaleFraction < 1.0f);
+ }
+
+ final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
+ final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
+
+ if (pixelFormat == PixelFormat.UNKNOWN) {
+ pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
+ && !(isWindowTranslucent && isShowWallpaper)
+ ? PixelFormat.RGB_565
+ : PixelFormat.RGBA_8888;
+ }
+
+ final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
+ && (!activity.fillsParent() || isWindowTranslucent);
+
+ builder.setTopActivityComponent(activity.mActivityComponent);
+ builder.setIsTranslucent(isTranslucent);
+ builder.setOrientation(activity.getTask().getConfiguration().orientation);
+ builder.setWindowingMode(task.getWindowingMode());
+ builder.setSystemUiVisibility(getSystemUiVisibility(task));
+ return true;
+ }
+
@Nullable
SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
float scaleFraction) {
@@ -288,63 +374,31 @@ class TaskSnapshotController {
return screenshotBuffer;
}
- @Nullable private TaskSnapshot snapshotTask(Task task) {
- if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
- }
- return null;
- }
-
- final ActivityRecord activity = findAppTokenForSnapshot(task);
- if (activity == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
- }
- return null;
- }
- if (activity.hasCommittedReparentToAnimationLeash()) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
- }
- return null;
- }
+ @Nullable
+ TaskSnapshot snapshotTask(Task task) {
+ return snapshotTask(task, SNAPSHOT_SCALE_AUTO, PixelFormat.UNKNOWN);
+ }
- final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
- final float scaleFraction = isLowRamDevice
- ? mPersister.getReducedScale()
- : mFullSnapshotScale;
+ @Nullable
+ TaskSnapshot snapshotTask(Task task, float scaleFraction, int pixelFormat) {
+ TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
- final WindowState mainWindow = activity.findMainWindow();
- if (mainWindow == null) {
- Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
+ if (!prepareTaskSnapshot(task, scaleFraction, pixelFormat, builder)) {
+ // Failed some pre-req. Has been logged.
return null;
}
- final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
- final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
- final int pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
- && !(isWindowTranslucent && isShowWallpaper)
- ? PixelFormat.RGB_565
- : PixelFormat.RGBA_8888;
+
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
- createTaskSnapshot(task, scaleFraction, pixelFormat);
+ createTaskSnapshot(task, builder.getScaleFraction(),
+ builder.getPixelFormat());
if (screenshotBuffer == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot for " + task);
- }
+ // Failed to acquire image. Has been logged.
return null;
}
- final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
- && (!activity.fillsParent() || isWindowTranslucent);
- return new TaskSnapshot(
- System.currentTimeMillis() /* id */,
- activity.mActivityComponent, screenshotBuffer.getGraphicBuffer(),
- screenshotBuffer.getColorSpace(),
- activity.getTask().getConfiguration().orientation,
- getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */,
- true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task),
- isTranslucent);
+ builder.setSnapshot(screenshotBuffer.getGraphicBuffer());
+ builder.setColorSpace(screenshotBuffer.getColorSpace());
+ return builder.build();
}
private boolean shouldDisableSnapshots() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 1484d6aeabef..f23659e9ca9f 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -202,7 +202,7 @@ class TaskSnapshotSurface implements StartingSurface {
final TaskDescription td = task.getTaskDescription();
if (td != null) {
- taskDescription.copyFrom(td);
+ taskDescription.copyFromPreserveHiddenFields(td);
}
taskBounds = new Rect();
task.getBounds(taskBounds);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d73cb50fe107..c95111fd4927 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -41,7 +41,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.logWithStack;
-import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
@@ -70,7 +69,6 @@ import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
-import com.android.server.policy.WindowManagerPolicy;
import com.android.server.protolog.common.ProtoLog;
import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -244,6 +242,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
+ private MagnificationSpec mLastMagnificationSpec;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -1726,6 +1726,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (shouldMagnify()) {
t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
.setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+ mLastMagnificationSpec = spec;
} else {
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).applyMagnificationSpec(t, spec);
@@ -1733,6 +1734,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ void clearMagnificationSpec(Transaction t) {
+ if (mLastMagnificationSpec != null) {
+ t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
+ .setPosition(mSurfaceControl, 0, 0);
+ }
+ mLastMagnificationSpec = null;
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).clearMagnificationSpec(t);
+ }
+ }
+
void prepareSurfaces() {
// If a leash has been set when the transaction was committed, then the leash reparent has
// been committed.
@@ -2151,15 +2163,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
void waitForAllWindowsDrawn() {
- final WindowManagerPolicy policy = mWmService.mPolicy;
forAllWindows(w -> {
- final boolean keyguard = policy.isKeyguardHostWindow(w.mAttrs);
- if (w.isVisibleLw() && (w.mActivityRecord != null || keyguard)) {
- w.mWinAnimator.mDrawState = DRAW_PENDING;
- // Force add to mResizingWindows.
- w.resetLastContentInsets();
- mWaitingForDrawn.add(w);
- }
+ w.requestDrawIfNeeded(mWaitingForDrawn);
}, true /* traverseTopToBottom */);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index acaaed9b615c..68de4cd84bbf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4511,6 +4511,7 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
+ public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63;
/**
* Used to denote that an integer field in a message will not be used.
@@ -4885,6 +4886,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
break;
}
+ case LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED: {
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = (DisplayContent) msg.obj;
+ displayContent.layoutAndAssignWindowLayersIfNeeded();
+ }
+ break;
+ }
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: exit");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6918c966a3ec..2a8de3f95f46 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1726,6 +1726,39 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
&& isDrawnLw() && !isAnimating(TRANSITION | PARENTS);
}
+ /** @see WindowManagerInternal#waitForAllWindowsDrawn */
+ void requestDrawIfNeeded(List<WindowState> outWaitingForDrawn) {
+ if (!isVisible()) {
+ return;
+ }
+ if (mActivityRecord != null) {
+ if (mActivityRecord.allDrawn) {
+ // The allDrawn of activity is reset when the visibility is changed to visible, so
+ // the content should be ready if allDrawn is set.
+ return;
+ }
+ if (mAttrs.type == TYPE_APPLICATION_STARTING) {
+ if (isDrawnLw()) {
+ // Unnecessary to redraw a drawn starting window.
+ return;
+ }
+ } else if (mActivityRecord.startingWindow != null) {
+ // If the activity has an active starting window, there is no need to wait for the
+ // main window.
+ return;
+ }
+ } else if (!mPolicy.isKeyguardHostWindow(mAttrs)) {
+ return;
+ // Always invalidate keyguard host window to make sure it shows the latest content
+ // because its visibility may not be changed.
+ }
+
+ mWinAnimator.mDrawState = DRAW_PENDING;
+ // Force add to {@link WindowManagerService#mResizingWindows}.
+ resetLastContentInsets();
+ outWaitingForDrawn.add(this);
+ }
+
@Override
void onMovedByResize() {
ProtoLog.d(WM_DEBUG_RESIZE, "onMovedByResize: Moving %s", this);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fd8094cc43dd..a34b7fdb911c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@ cc_library_static {
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_security_VerityUtils.cpp",
"com_android_server_SerialService.cpp",
+ "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_storage_AppFuseBridge.cpp",
"com_android_server_SystemServer.cpp",
"com_android_server_TestNetworkService.cpp",
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index dcff5a11aca0..6811e6d0e6f2 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -410,6 +410,21 @@ static jlong vibratorGetCapabilities(JNIEnv*, jclass) {
return 0;
}
+static void vibratorAlwaysOnEnable(JNIEnv* env, jclass, jlong id, jlong effect, jlong strength) {
+ auto status = halCall(&aidl::IVibrator::alwaysOnEnable, id,
+ static_cast<aidl::Effect>(effect), static_cast<aidl::EffectStrength>(strength));
+ if (!status.isOk()) {
+ ALOGE("vibratortAlwaysOnEnable command failed (%s).", status.toString8().string());
+ }
+}
+
+static void vibratorAlwaysOnDisable(JNIEnv* env, jclass, jlong id) {
+ auto status = halCall(&aidl::IVibrator::alwaysOnDisable, id);
+ if (!status.isOk()) {
+ ALOGE("vibratorAlwaysOnDisable command failed (%s).", status.toString8().string());
+ }
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
@@ -422,6 +437,8 @@ static const JNINativeMethod method_table[] = {
{ "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
{ "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
{ "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
+ { "vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
+ { "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
new file mode 100644
index 000000000000..774534f23b8c
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+#include <sstream>
+
+#include "core_jni_helpers.h"
+#include <media/AudioSystem.h>
+
+namespace android {
+
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/AudioSessionProviderImpl"
+#define SESSION_CLASSNAME PACKAGE "/SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession"
+
+jobject acquireAudioSession(
+ JNIEnv* env,
+ jobject clazz) {
+
+ audio_session_t session;
+ audio_io_handle_t ioHandle;
+ audio_devices_t device;
+
+ status_t status = AudioSystem::acquireSoundTriggerSession(&session,
+ &ioHandle,
+ &device);
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::acquireSoundTriggerSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ return nullptr;
+ }
+
+ jclass cls = FindClassOrDie(env, SESSION_CLASSNAME);
+ jmethodID ctor = GetMethodIDOrDie(env, cls, "<init>", "(III)V");
+ return env->NewObject(cls,
+ ctor,
+ static_cast<int>(session),
+ static_cast<int>(ioHandle),
+ static_cast<int>(device));
+}
+
+void releaseAudioSession(JNIEnv* env, jobject clazz, jint handle) {
+ status_t status =
+ AudioSystem::releaseSoundTriggerSession(static_cast<audio_session_t>(handle));
+
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::releaseAudioSystemSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ }
+}
+
+const JNINativeMethod g_methods[] = {
+ {"acquireSession", "()L" SESSION_CLASSNAME ";",
+ reinterpret_cast<void*>(acquireAudioSession)},
+ {"releaseSession", "(I)V",
+ reinterpret_cast<void*>(releaseAudioSession)},
+};
+
+} // namespace
+
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env) {
+ return RegisterMethodsOrDie(env,
+ CLASSNAME,
+ g_methods,
+ NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d25baa9..165edf15ca23 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,8 @@ int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_AppCompactor(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env);
};
using namespace android;
@@ -105,5 +107,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_security_VerityUtils(env);
register_android_server_am_AppCompactor(env);
register_android_server_am_LowMemDetector(env);
+ register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ env);
return JNI_VERSION_1_4;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f3ac7d6d3cc9..f05bbe215b78 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -105,6 +105,7 @@ import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.gpu.GpuService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.incident.IncidentCompanionService;
+import com.android.server.incremental.IncrementalManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.InputMethodSystemProperty;
@@ -148,6 +149,7 @@ import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -208,8 +210,8 @@ public final class SystemServer {
"com.android.server.print.PrintManagerService";
private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
"com.android.server.companion.CompanionDeviceManagerService";
- private static final String STATS_COMPANION_SERVICE_LIFECYCLE_CLASS =
- "com.android.server.stats.StatsCompanionService$Lifecycle";
+ private static final String STATS_COMPANION_LIFECYCLE_CLASS =
+ "com.android.server.stats.StatsCompanion$Lifecycle";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
@@ -323,6 +325,7 @@ public final class SystemServer {
private ContentResolver mContentResolver;
private EntropyMixer mEntropyMixer;
private DataLoaderManagerService mDataLoaderManagerService;
+ private IncrementalManagerService mIncrementalManagerService;
private boolean mOnlyCore;
private boolean mFirstBoot;
@@ -705,6 +708,11 @@ public final class SystemServer {
DataLoaderManagerService.class);
t.traceEnd();
+ // Incremental service needs to be started before package manager
+ t.traceBegin("StartIncrementalManagerService");
+ mIncrementalManagerService = IncrementalManagerService.start(mSystemContext);
+ t.traceEnd();
+
// Power manager needs to be started early because other services need it.
// Native daemons may be watching for it to be registered so it must be ready
// to handle incoming binder calls immediately (including being able to verify
@@ -1555,6 +1563,10 @@ public final class SystemServer {
}
t.traceEnd();
+ t.traceBegin("StartSoundTriggerMiddlewareService");
+ mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ t.traceEnd();
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BROADCAST_RADIO)) {
t.traceBegin("StartBroadcastRadioService");
mSystemServiceManager.startService(BroadcastRadioService.class);
@@ -1950,8 +1962,8 @@ public final class SystemServer {
}
// Statsd helper
- t.traceBegin("StartStatsCompanionService");
- mSystemServiceManager.startService(STATS_COMPANION_SERVICE_LIFECYCLE_CLASS);
+ t.traceBegin("StartStatsCompanion");
+ mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
t.traceEnd();
// Incidentd and dumpstated helper
@@ -2061,6 +2073,12 @@ public final class SystemServer {
mPackageManagerService.systemReady();
t.traceEnd();
+ if (mIncrementalManagerService != null) {
+ t.traceBegin("MakeIncrementalManagerServiceReady");
+ mIncrementalManagerService.systemReady();
+ t.traceEnd();
+ }
+
t.traceBegin("MakeDisplayManagerServiceReady");
try {
// TODO: use boot phase and communicate these flags some other way
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 3910993d9c27..015e574f2309 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -28,6 +28,7 @@ android_test {
"services.net",
"services.usage",
"guava",
+ "androidx.test.core",
"androidx.test.runner",
"androidx.test.rules",
"mockito-target-minus-junit4",
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
index 192c50c7dc19..e9c5ce7127de 100644
--- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -11,28 +11,48 @@
* 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
+ * limitations under the License.
*/
package com.android.server;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+
import android.content.Context;
import android.location.Country;
import android.location.CountryListener;
import android.location.ICountryListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
-import android.test.AndroidTestCase;
-public class CountryDetectorServiceTest extends AndroidTestCase {
- private class CountryListenerTester extends ICountryListener.Stub {
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CountryDetectorServiceTest {
+
+ private static class CountryListenerTester extends ICountryListener.Stub {
private Country mCountry;
@Override
- public void onCountryDetected(Country country) throws RemoteException {
+ public void onCountryDetected(Country country) {
mCountry = country;
}
- public Country getCountry() {
+ Country getCountry() {
return mCountry;
}
@@ -41,12 +61,11 @@ public class CountryDetectorServiceTest extends AndroidTestCase {
}
}
- private class CountryDetectorServiceTester extends CountryDetectorService {
-
+ private static class CountryDetectorServiceTester extends CountryDetectorService {
private CountryListener mListener;
- public CountryDetectorServiceTester(Context context) {
- super(context);
+ CountryDetectorServiceTester(Context context, Handler handler) {
+ super(context, handler);
}
@Override
@@ -59,51 +78,77 @@ public class CountryDetectorServiceTest extends AndroidTestCase {
mListener = listener;
}
- public boolean isListenerSet() {
+ boolean isListenerSet() {
return mListener != null;
}
}
- public void testAddRemoveListener() throws RemoteException {
- CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
- serviceTester.systemRunning();
- waitForSystemReady(serviceTester);
- CountryListenerTester listenerTester = new CountryListenerTester();
- serviceTester.addCountryListener(listenerTester);
- assertTrue(serviceTester.isListenerSet());
- serviceTester.removeCountryListener(listenerTester);
- assertFalse(serviceTester.isListenerSet());
+ @Rule
+ public final Expect expect = Expect.create();
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
+ @Spy
+ private Handler mHandler = new Handler(Looper.myLooper());
+ private CountryDetectorServiceTester mCountryDetectorService;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
}
- public void testNotifyListeners() throws RemoteException {
- CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
- CountryListenerTester listenerTesterA = new CountryListenerTester();
- CountryListenerTester listenerTesterB = new CountryListenerTester();
- Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
- serviceTester.systemRunning();
- waitForSystemReady(serviceTester);
- serviceTester.addCountryListener(listenerTesterA);
- serviceTester.addCountryListener(listenerTesterB);
- serviceTester.notifyReceivers(country);
- assertTrue(serviceTester.isListenerSet());
- assertTrue(listenerTesterA.isNotified());
- assertTrue(listenerTesterB.isNotified());
- serviceTester.removeCountryListener(listenerTesterA);
- serviceTester.removeCountryListener(listenerTesterB);
- assertFalse(serviceTester.isListenerSet());
+ @Before
+ public void setUp() {
+ mCountryDetectorService = new CountryDetectorServiceTester(mContext, mHandler);
+
+ // Immediately invoke run on the Runnable posted to the handler
+ doAnswer(invocation -> {
+ Message message = invocation.getArgument(0);
+ message.getCallback().run();
+ return true;
+ }).when(mHandler).sendMessageAtTime(any(Message.class), anyLong());
}
- private void waitForSystemReady(CountryDetectorService service) {
- int count = 5;
- while (count-- > 0) {
- try {
- Thread.sleep(500);
- } catch (Exception e) {
- }
- if (service.isSystemReady()) {
- return;
- }
- }
- throw new RuntimeException("Wait System Ready timeout");
+ @Test
+ public void countryListener_add_successful() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ mCountryDetectorService.systemRunning();
+ expect.that(mCountryDetectorService.isListenerSet()).isFalse();
+ mCountryDetectorService.addCountryListener(countryListener);
+
+ expect.that(mCountryDetectorService.isListenerSet()).isTrue();
+ }
+
+ @Test
+ public void countryListener_remove_successful() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ mCountryDetectorService.systemRunning();
+ mCountryDetectorService.addCountryListener(countryListener);
+ expect.that(mCountryDetectorService.isListenerSet()).isTrue();
+ mCountryDetectorService.removeCountryListener(countryListener);
+
+ expect.that(mCountryDetectorService.isListenerSet()).isFalse();
+ }
+
+ @Test
+ public void countryListener_notify_successful() throws RemoteException {
+ CountryListenerTester countryListenerA = new CountryListenerTester();
+ CountryListenerTester countryListenerB = new CountryListenerTester();
+ Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+
+ mCountryDetectorService.systemRunning();
+ mCountryDetectorService.addCountryListener(countryListenerA);
+ mCountryDetectorService.addCountryListener(countryListenerB);
+ expect.that(countryListenerA.isNotified()).isFalse();
+ expect.that(countryListenerB.isNotified()).isFalse();
+ mCountryDetectorService.notifyReceivers(country);
+
+ expect.that(countryListenerA.isNotified()).isTrue();
+ expect.that(countryListenerB.isNotified()).isTrue();
+ expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue();
+ expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
index b7079124fb79..538e2d51e88f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
@@ -24,22 +24,21 @@ import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityService;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
-import android.util.DisplayMetrics;
-import android.view.GestureDetector;
import android.view.MotionEvent;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
/**
- * Tests for AccessibilityGestureDetector
+ * Tests for GestureManifold
*/
-public class AccessibilityGestureDetectorTest {
+public class GestureManifoldTest {
// Constants for testRecognizeGesturePath()
private static final PointF PATH_START = new PointF(300f, 300f);
@@ -47,24 +46,21 @@ public class AccessibilityGestureDetectorTest {
private static final long PATH_STEP_MILLISEC = 100;
// Data used by all tests
- private AccessibilityGestureDetector mDetector;
- private AccessibilityGestureDetector.Listener mResultListener;
+ private GestureManifold mManifold;
+ private TouchState mState;
+ private GestureManifold.Listener mResultListener;
@Before
public void setUp() {
- // Construct a mock Context.
- DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class);
- displayMetricsMock.xdpi = 500;
- displayMetricsMock.ydpi = 500;
- Resources mockResources = mock(Resources.class);
- when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock);
- Context contextMock = mock(Context.class);
- when(contextMock.getResources()).thenReturn(mockResources);
-
- // Construct a testable AccessibilityGestureDetector.
- mResultListener = mock(AccessibilityGestureDetector.Listener.class);
- GestureDetector doubleTapDetectorMock = mock(GestureDetector.class);
- mDetector = new AccessibilityGestureDetector(contextMock, mResultListener, doubleTapDetectorMock);
+ Context context = InstrumentationRegistry.getContext();
+ // Construct a testable GestureManifold.
+ mResultListener = mock(GestureManifold.Listener.class);
+ mState = new TouchState();
+ mManifold = new GestureManifold(context, mResultListener, mState);
+ // Play the role of touch explorer in updating the shared state.
+ when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());
+
+
}
@@ -141,8 +137,8 @@ public class AccessibilityGestureDetectorTest {
// For each path step from start (non-inclusive) to end ... add a motion point.
for (int step = 1; step < numSteps; ++step) {
path.add(new PointF(
- (start.x + (stepX * (float) step)),
- (start.y + (stepY * (float) step))));
+ (start.x + (stepX * (float) step)),
+ (start.y + (stepY * (float) step))));
}
}
@@ -170,12 +166,22 @@ public class AccessibilityGestureDetectorTest {
point.x, point.y, 0);
// Send event.
- mDetector.onMotionEvent(event, event, policyFlags);
+ mState.onReceivedMotionEvent(event);
+ mManifold.onMotionEvent(event, event, policyFlags);
eventTimeMs += PATH_STEP_MILLISEC;
+ if (mState.isClear()) {
+ mState.startTouchInteracting();
+ }
}
+ mState.clear();
// Check that correct gesture was recognized.
verify(mResultListener).onGestureCompleted(
argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
}
+
+ private boolean onGestureStarted() {
+ mState.startGestureDetecting();
+ return false;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 4b1ec6fe032b..a4ceadb3028b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -74,7 +74,7 @@ public class TouchExplorerTest {
private TouchExplorer mTouchExplorer;
private long mLastDownTime = Integer.MIN_VALUE;
- // mock package-private AccessibilityGestureDetector class
+ // mock package-private GestureManifold class
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
@@ -108,7 +108,7 @@ public class TouchExplorerTest {
public void setUp() {
Context context = InstrumentationRegistry.getContext();
AccessibilityManagerService ams = new AccessibilityManagerService(context);
- AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
+ GestureManifold detector = mock(GestureManifold.class);
mCaptor = new EventCaptor();
mTouchExplorer = new TouchExplorer(context, ams, detector);
mTouchExplorer.setNext(mCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 49412bc0c559..2ce17a1d0cea 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -349,10 +349,21 @@ public class ActivityManagerServiceTest {
verifyUidRangesNoOverlap(range, range2);
verifyIsolatedUidAllocator(range2);
- // Free both, then try to allocate the maximum number of UID ranges
+ // Free both
allocator.freeUidRangeLocked(appInfo);
allocator.freeUidRangeLocked(appInfo2);
+ // Verify for a secondary user
+ ApplicationInfo appInfo3 = new ApplicationInfo();
+ appInfo3.processName = "com.android.test.app";
+ appInfo3.uid = 1010000;
+ final IsolatedUidRange range3 = allocator.getOrCreateIsolatedUidRangeLocked(
+ appInfo3.processName, appInfo3.uid);
+ validateAppZygoteIsolatedUidRange(range3);
+ verifyIsolatedUidAllocator(range3);
+
+ allocator.freeUidRangeLocked(appInfo3);
+ // Try to allocate the maximum number of UID ranges
int maxNumUidRanges = (Process.LAST_APP_ZYGOTE_ISOLATED_UID
- Process.FIRST_APP_ZYGOTE_ISOLATED_UID + 1) / Process.NUM_UIDS_PER_APP_ZYGOTE;
for (int i = 0; i < maxNumUidRanges; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index a47a5671ccb3..e90cb4641752 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -16,7 +16,11 @@
package com.android.server.attention;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
import static com.android.server.attention.AttentionManagerService.ATTENTION_CACHE_BUFFER_SIZE;
+import static com.android.server.attention.AttentionManagerService.DEFAULT_STALE_AFTER_MILLIS;
+import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFTER_MILLIS;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +39,7 @@ import android.os.IBinder;
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.provider.DeviceConfig;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
@@ -180,6 +185,45 @@ public class AttentionManagerServiceTest {
assertThat(buffer.get(0)).isEqualTo(cache);
}
+ @Test
+ public void testGetStaleAfterMillis_handlesGoodFlagValue() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "123", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(123);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_1() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "-123", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_2() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "15000", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_3() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "abracadabra", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_4() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "15_000L", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
private class MockIAttentionService implements IAttentionService {
public void checkAttention(IAttentionCallback callback) throws RemoteException {
callback.onSuccess(0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java
deleted file mode 100644
index 18b91b0b1009..000000000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * 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.server.integrity.serializer;
-
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.Formula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-/** Unit tests for {@link RuleIndexTypeIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexTypeIdentifierTest {
-
- @Test
- public void getIndexType_nullRule() {
- Rule rule = null;
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */
- "Indexing type cannot be determined for null rule.",
- () -> RuleIndexTypeIdentifier.getIndexType(rule));
- }
-
- @Test
- public void getIndexType_invalidFormula() {
- Rule rule = new Rule(getInvalidFormula(), Rule.DENY);
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */ "Invalid formula tag type.",
- () -> RuleIndexTypeIdentifier.getIndexType(rule));
- }
-
- @Test
- public void getIndexType_ruleContainingPackageNameFormula() {
- String packageName = "com.test.app";
- String installerName = "com.test.installer";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.PACKAGE_NAME_INDEXED);
- }
-
- @Test
- public void getIndexType_ruleContainingAppCertificateFormula() {
- String appCertificate = "cert1";
- String installerName = "com.test.installer";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.APP_CERTIFICATE_INDEXED);
- }
-
- @Test
- public void getIndexType_ruleWithUnindexedCompoundFormula() {
- String installerCertificate = "cert1";
- String installerName = "com.test.installer";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- installerCertificate,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.NOT_INDEXED);
- }
-
- @Test
- public void getIndexType_rulContainingCompoundFormulaWithIntAndBoolean() {
- int appVersion = 12;
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.BooleanAtomicFormula(
- AtomicFormula.PRE_INSTALLED,
- /* booleanValue= */ true),
- new AtomicFormula.IntAtomicFormula(
- AtomicFormula.VERSION_CODE,
- AtomicFormula.EQ,
- appVersion))),
- Rule.DENY);
-
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.NOT_INDEXED);
- }
-
- @Test
- public void getIndexType_negatedRuleContainingPackageNameFormula() {
- String packageName = "com.test.app";
- String installerName = "com.test.installer";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Arrays.asList(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false))))),
- Rule.DENY);
-
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.NOT_INDEXED);
- }
-
- private Formula getInvalidFormula() {
- return new Formula() {
- @Override
- public boolean isSatisfied(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public int getTag() {
- return 4;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
new file mode 100644
index 000000000000..b25f3bc5c3fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.server.integrity.serializer;
+
+import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
+import android.content.integrity.Rule;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
+@RunWith(JUnit4.class)
+public class RuleIndexingDetailsIdentifierTest {
+
+ private static final String SAMPLE_APP_CERTIFICATE = "testcert";
+ private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
+ private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
+ private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
+
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ SAMPLE_PACKAGE_NAME,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ SAMPLE_APP_CERTIFICATE,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_CERTIFICATE,
+ SAMPLE_INSTALLER_CERTIFICATE,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
+ new AtomicFormula.IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 12);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
+ new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, /* booleanValue= */
+ true);
+
+
+ private static final Rule RULE_WITH_PACKAGE_NAME =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_PACKAGE_NAME,
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
+ Rule.DENY);
+ private static final Rule RULE_WITH_APP_CERTIFICATE =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
+ Rule.DENY);
+ private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME,
+ ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
+ Rule.DENY);
+
+ private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_VERSION_CODE,
+ ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
+ Rule.DENY);
+
+ @Test
+ public void getIndexType_nullRule() {
+ List<Rule> ruleList = null;
+
+ assertExpectException(
+ IllegalArgumentException.class,
+ /* expectedExceptionMessageRegex= */
+ "Index buckets cannot be created for null rule list.",
+ () -> splitRulesIntoIndexBuckets(ruleList));
+ }
+
+ @Test
+ public void getIndexType_invalidFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
+
+ assertExpectException(
+ IllegalArgumentException.class,
+ /* expectedExceptionMessageRegex= */ "Invalid formula tag type.",
+ () -> splitRulesIntoIndexBuckets(ruleList));
+ }
+
+ @Test
+ public void getIndexType_ruleContainingPackageNameFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_PACKAGE_NAME);
+
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ // Verify the resulting map content.
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(NOT_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
+ assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
+ .containsExactly(RULE_WITH_PACKAGE_NAME);
+ }
+
+ @Test
+ public void getIndexType_ruleContainingAppCertificateFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_APP_CERTIFICATE);
+
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(NOT_INDEXED)).isEmpty();
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
+ .containsExactly(SAMPLE_APP_CERTIFICATE);
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
+ .containsExactly(RULE_WITH_APP_CERTIFICATE);
+ }
+
+ @Test
+ public void getIndexType_ruleWithUnindexedCompoundFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
+
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(NOT_INDEXED).keySet()).containsExactly("N/A");
+ assertThat(result.get(NOT_INDEXED).get("N/A"))
+ .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
+ }
+
+ @Test
+ public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
+
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(NOT_INDEXED).keySet()).containsExactly("N/A");
+ assertThat(result.get(NOT_INDEXED).get("N/A"))
+ .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
+ }
+
+ @Test
+ public void getIndexType_negatedRuleContainingPackageNameFormula() {
+ Rule negatedRule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.NOT,
+ Arrays.asList(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_PACKAGE_NAME,
+ ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
+ Rule.DENY);
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(negatedRule);
+
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(NOT_INDEXED).keySet()).containsExactly("N/A");
+ assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
+ }
+
+ @Test
+ public void getIndexType_allRulesTogether() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_PACKAGE_NAME);
+ ruleList.add(RULE_WITH_APP_CERTIFICATE);
+ ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
+ ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
+
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+
+ assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
+ assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
+ .containsExactly(RULE_WITH_PACKAGE_NAME);
+
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
+ .containsExactly(SAMPLE_APP_CERTIFICATE);
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
+ .containsExactly(RULE_WITH_APP_CERTIFICATE);
+
+ assertThat(result.get(NOT_INDEXED).keySet()).containsExactly("N/A");
+ assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(
+ RULE_WITH_INSTALLER_RESTRICTIONS, RULE_WITH_NONSTRING_RESTRICTIONS);
+ }
+
+ private Formula getInvalidFormula() {
+ return new Formula() {
+ @Override
+ public boolean isSatisfied(AppInstallMetadata appInstallMetadata) {
+ return false;
+ }
+
+ @Override
+ public int getTag() {
+ return 4;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+
+ @NonNull
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ }
+ };
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
new file mode 100644
index 000000000000..0f915dbdcf6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.server.om
+
+import org.mockito.Answers
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.stubbing.Answer
+import org.mockito.stubbing.Stubber
+
+// TODO(chiuwinson): Move this entire file to a shared utility module
+// TODO(b/135203078): De-dupe utils added for overlays vs package refactor
+object MockitoUtils {
+ val ANSWER_THROWS = Answer<Any?> {
+ when (val name = it.method.name) {
+ "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ else -> {
+ val arguments = it.arguments
+ ?.takeUnless { it.isEmpty() }
+ ?.joinToString()
+ ?.let {
+ "with $it"
+ }
+ .orEmpty()
+
+ throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
+ "$arguments should not be called")
+ }
+ }
+ }
+}
+
+inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
+
+fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
+fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
+
+@Suppress("UNCHECKED_CAST")
+fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) =
+ Mockito.`when`(mock).thenAnswer { block(it) }
+
+fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
+
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+ val swappingAnswer = object : Answer<Any?> {
+ var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
+
+ override fun answer(invocation: InvocationOnMock?): Any? {
+ return delegate.answer(invocation)
+ }
+ }
+
+ return Mockito.mock(T::class.java, swappingAnswer).apply(block)
+ .also {
+ // To allow when() usage inside block, only swap to throwing afterwards
+ swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
new file mode 100644
index 000000000000..ef1294819f34
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.server.om
+
+import android.content.pm.parsing.AndroidPackage
+import android.net.Uri
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.testng.Assert.assertThrows
+
+@RunWith(Parameterized::class)
+class OverlayReferenceMapperTests {
+
+ companion object {
+ private const val TARGET_PACKAGE_NAME = "com.test.target"
+ private const val OVERLAY_PACKAGE_NAME = "com.test.overlay"
+ private const val ACTOR_PACKAGE_NAME = "com.test.actor"
+ private const val ACTOR_NAME = "overlay://test/actorName"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "deferRebuild {0}")
+ fun parameters() = arrayOf(true, false)
+ }
+
+ private lateinit var mapper: OverlayReferenceMapper
+
+ @JvmField
+ @Parameterized.Parameter(0)
+ var deferRebuild = false
+
+ @Before
+ fun initMapper() {
+ mapper = mapper()
+ }
+
+ @Test
+ fun targetWithOverlay() {
+ val target = mockTarget()
+ val overlay = mockOverlay()
+ val existing = mapper.addInOrder(overlay)
+ assertEmpty()
+ mapper.addInOrder(target, existing = existing)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+ mapper.remove(target)
+ assertEmpty()
+ }
+
+ @Test
+ fun targetWithMultipleOverlays() {
+ val target = mockTarget()
+ val overlay0 = mockOverlay(0)
+ val overlay1 = mockOverlay(1)
+ mapper = mapper(
+ overlayToTargetToOverlayables = mapOf(
+ overlay0.packageName to mapOf(
+ target.packageName to target.overlayables.keys
+ ),
+ overlay1.packageName to mapOf(
+ target.packageName to target.overlayables.keys
+ )
+ )
+ )
+ val existing = mapper.addInOrder(overlay0, overlay1)
+ assertEmpty()
+ mapper.addInOrder(target, existing = existing)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
+ mapper.remove(overlay0)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
+ mapper.remove(target)
+ assertEmpty()
+ }
+
+ @Test
+ fun targetWithoutOverlay() {
+ val target = mockTarget()
+ mapper.addInOrder(target)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ mapper.remove(target)
+ assertEmpty()
+ }
+
+ @Test
+ fun overlayWithTarget() {
+ val target = mockTarget()
+ val overlay = mockOverlay()
+ val existing = mapper.addInOrder(target)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ mapper.addInOrder(overlay, existing = existing)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+ mapper.remove(overlay)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ }
+
+ @Test
+ fun overlayWithMultipleTargets() {
+ val target0 = mockTarget(0)
+ val target1 = mockTarget(1)
+ val overlay = mockOverlay()
+ mapper = mapper(
+ overlayToTargetToOverlayables = mapOf(
+ overlay.packageName to mapOf(
+ target0.packageName to target0.overlayables.keys,
+ target1.packageName to target1.overlayables.keys
+ )
+ )
+ )
+ mapper.addInOrder(target0, target1, overlay)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
+ mapper.remove(target0)
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
+ mapper.remove(target1)
+ assertEmpty()
+ }
+
+ @Test
+ fun overlayWithoutTarget() {
+ val overlay = mockOverlay()
+ mapper.addInOrder(overlay)
+ // An overlay can only have visibility exposed through its target
+ assertEmpty()
+ mapper.remove(overlay)
+ assertEmpty()
+ }
+
+ private fun OverlayReferenceMapper.addInOrder(
+ vararg pkgs: AndroidPackage,
+ existing: MutableMap<String, AndroidPackage> = mutableMapOf()
+ ) = pkgs.fold(existing) { map, pkg ->
+ addPkg(pkg, map)
+ map[pkg.packageName] = pkg
+ return@fold map
+ }
+
+ private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+
+ private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
+ val expected = pairs.associate { it }
+ .mapValues { pair -> pair.value.map { it.packageName }.toSet() }
+
+ // This validates the API exposed for querying the relationships
+ expected.forEach { (actorPkgName, expectedPkgNames) ->
+ expectedPkgNames.forEach { expectedPkgName ->
+ if (deferRebuild) {
+ assertThrows(IllegalStateException::class.java) {
+ mapper.isValidActor(expectedPkgName, actorPkgName)
+ }
+ mapper.rebuildIfDeferred()
+ deferRebuild = false
+ }
+
+ assertThat(mapper.isValidActor(expectedPkgName, actorPkgName)).isTrue()
+ }
+ }
+
+ // This asserts no other relationships are defined besides those tested above
+ assertThat(mapper.actorPkgToPkgs).containsExactlyEntriesIn(expected)
+ }
+
+ private fun assertEmpty() = assertMapping()
+
+ private fun mapper(
+ namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run {
+ mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME))
+ },
+ overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf(
+ mockOverlay().packageName to mapOf(
+ mockTarget().run { packageName to overlayables.keys }
+ )
+ )
+ ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider {
+ override fun getActorPkg(actor: String?) =
+ OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first
+
+ override fun getTargetToOverlayables(pkg: AndroidPackage) =
+ overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap()
+ })
+
+ private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { "$TARGET_PACKAGE_NAME$increment" }
+ whenever(overlayables) { mapOf("overlayableName$increment" to ACTOR_NAME) }
+ whenever(toString()) { "Package{$packageName}" }
+ }
+
+ private fun mockOverlay(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { "$OVERLAY_PACKAGE_NAME$increment" }
+ whenever(overlayables) { emptyMap<String, String>() }
+ whenever(toString()) { "Package{$packageName}" }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 4fc625a1e1fb..82bbdcba5bc1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,6 +35,11 @@ import android.content.pm.parsing.ParsingPackage;
import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.om.OverlayReferenceMapper;
import org.junit.Before;
import org.junit.Test;
@@ -43,11 +48,18 @@ import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
@RunWith(JUnit4.class)
public class AppsFilterTest {
private static final int DUMMY_CALLING_UID = 10345;
private static final int DUMMY_TARGET_UID = 10556;
+ private static final int DUMMY_ACTOR_UID = 10656;
+ private static final int DUMMY_OVERLAY_UID = 10756;
+ private static final int DUMMY_ACTOR_TWO_UID = 10856;
@Mock
AppsFilter.FeatureConfig mFeatureConfigMock;
@@ -117,7 +129,7 @@ public class AppsFilterTest {
@Test
public void testSystemReadyPropogates() throws Exception {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
appsFilter.onSystemReady();
verify(mFeatureConfigMock).onSystemReady();
}
@@ -125,7 +137,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_FilterMatches() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID);
@@ -138,7 +151,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_NoMatchingAction_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -151,7 +165,8 @@ public class AppsFilterTest {
@Test
public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -169,7 +184,8 @@ public class AppsFilterTest {
@Test
public void testNoQueries_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -182,7 +198,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryable_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
@@ -195,7 +212,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryableByDevice_SystemCaller_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -209,7 +227,8 @@ public class AppsFilterTest {
@Test
public void testForceQueryableByDevice_NonSystemCaller_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -224,7 +243,8 @@ public class AppsFilterTest {
public void testSystemQueryable_DoesntFilter() {
final AppsFilter appsFilter =
new AppsFilter(mFeatureConfigMock, new String[]{},
- true /* system force queryable */);
+ true /* system force queryable */, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -238,7 +258,8 @@ public class AppsFilterTest {
@Test
public void testQueriesPackage_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -253,7 +274,8 @@ public class AppsFilterTest {
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
.thenReturn(false);
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(
appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -266,20 +288,22 @@ public class AppsFilterTest {
@Test
public void testSystemUid_DoesntFilter() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0));
- assertFalse(appsFilter.shouldFilterApplication(
- Process.FIRST_APPLICATION_UID - 1, null, target, 0));
+ assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1,
+ null, target, 0));
}
@Test
public void testNonSystemUid_NoCallingSetting_Filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -290,7 +314,8 @@ public class AppsFilterTest {
@Test
public void testNoTargetPackage_filters() {
final AppsFilter appsFilter =
- new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
PackageSetting target = new PackageSettingBuilder()
.setName("com.some.package")
@@ -304,6 +329,127 @@ public class AppsFilterTest {
assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
}
+ @Test
+ public void testActsOnTargetOfOverlay() {
+ final String actorName = "overlay://test/actorName";
+
+ ParsingPackage target = pkg("com.some.package.target")
+ .addOverlayable("overlayableName", actorName);
+ ParsingPackage overlay = pkg("com.some.package.overlay")
+ .setIsOverlay(true)
+ .setOverlayTarget(target.getPackageName())
+ .setOverlayTargetName("overlayableName");
+ ParsingPackage actor = pkg("com.some.package.actor");
+
+ final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+ new OverlayReferenceMapper.Provider() {
+ @Nullable
+ @Override
+ public String getActorPkg(String actorString) {
+ if (actorName.equals(actorString)) {
+ return actor.getPackageName();
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getTargetToOverlayables(
+ @NonNull AndroidPackage pkg) {
+ if (overlay.getPackageName().equals(pkg.getPackageName())) {
+ Map<String, Set<String>> map = new ArrayMap<>();
+ Set<String> set = new ArraySet<>();
+ set.add(overlay.getOverlayTargetName());
+ map.put(overlay.getOverlayTarget(), set);
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+ });
+ appsFilter.onSystemReady();
+
+ PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+ PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+ PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID);
+
+ // Actor can see both target and overlay
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+ targetSetting, 0));
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+ overlaySetting, 0));
+
+ // But target/overlay can't see each other
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+ overlaySetting, 0));
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+ targetSetting, 0));
+
+ // And can't see the actor
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+ actorSetting, 0));
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+ actorSetting, 0));
+ }
+
+ @Test
+ public void testActsOnTargetOfOverlayThroughSharedUser() {
+ final String actorName = "overlay://test/actorName";
+
+ ParsingPackage target = pkg("com.some.package.target")
+ .addOverlayable("overlayableName", actorName);
+ ParsingPackage overlay = pkg("com.some.package.overlay")
+ .setIsOverlay(true)
+ .setOverlayTarget(target.getPackageName())
+ .setOverlayTargetName("overlayableName");
+ ParsingPackage actorOne = pkg("com.some.package.actor.one");
+ ParsingPackage actorTwo = pkg("com.some.package.actor.two");
+
+ final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+ new OverlayReferenceMapper.Provider() {
+ @Nullable
+ @Override
+ public String getActorPkg(String actorString) {
+ // Only actorOne is mapped as a valid actor
+ if (actorName.equals(actorString)) {
+ return actorOne.getPackageName();
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getTargetToOverlayables(
+ @NonNull AndroidPackage pkg) {
+ if (overlay.getPackageName().equals(pkg.getPackageName())) {
+ Map<String, Set<String>> map = new ArrayMap<>();
+ Set<String> set = new ArraySet<>();
+ set.add(overlay.getOverlayTargetName());
+ map.put(overlay.getOverlayTarget(), set);
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+ });
+ appsFilter.onSystemReady();
+
+ PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+ PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+ PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID);
+ PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo,
+ DUMMY_ACTOR_TWO_UID);
+
+ SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
+ actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags);
+ actorSharedSetting.addPackage(actorOneSetting);
+ actorSharedSetting.addPackage(actorTwoSetting);
+
+ // actorTwo can see both target and overlay
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+ targetSetting, 0));
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+ overlaySetting, 0));
+ }
+
private interface WithSettingBuilder {
PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 5baeedefef05..2473997a61c9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,7 +22,7 @@ import android.util.SparseArray;
import java.io.File;
-class PackageSettingBuilder {
+public class PackageSettingBuilder {
private String mName;
private String mRealName;
private String mCodePath;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
index 683278b699c0..3db832b24236 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -26,6 +26,7 @@ import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE;
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA;
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM;
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
import static org.junit.Assert.assertEquals;
@@ -262,39 +263,25 @@ public class UserSystemPackageInstallerTest {
// No implicit whitelist, so only install pkg1.
boolean implicit = false;
- boolean isSysUser = false;
assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg1, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg1, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg2, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg2, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg3, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg3, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg4, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg4, pkgBitSetMap, userWhitelist, implicit));
// Use implicit whitelist, so install pkg1 and pkg4
implicit = true;
- isSysUser = false;
assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg1, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg1, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg2, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg2, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg3, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg3, pkgBitSetMap, userWhitelist, implicit));
assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg4, pkgBitSetMap, userWhitelist, implicit, isSysUser));
-
- // For user 0 specifically, we always implicitly whitelist.
- implicit = false;
- isSysUser = true;
- assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg1, pkgBitSetMap, userWhitelist, implicit, isSysUser));
- assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg2, pkgBitSetMap, userWhitelist, implicit, isSysUser));
- assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg3, pkgBitSetMap, userWhitelist, implicit, isSysUser));
- assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg4, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg4, pkgBitSetMap, userWhitelist, implicit));
}
/**
@@ -400,30 +387,42 @@ public class UserSystemPackageInstallerTest {
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG);
assertTrue(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertTrue(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
+ assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
+
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM);
+ assertFalse(mUserSystemPackageInstaller.isLogMode());
+ assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA);
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertTrue(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(
@@ -431,6 +430,7 @@ public class UserSystemPackageInstallerTest {
assertTrue(mUserSystemPackageInstaller.isLogMode());
assertTrue(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST
@@ -438,6 +438,7 @@ public class UserSystemPackageInstallerTest {
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertTrue(mUserSystemPackageInstaller.isEnforceMode());
assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
new file mode 100644
index 000000000000..5a2ce4540b82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.audio.common.V2_0.Uuid;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+ private static final String TAG = "ConversionUtilTest";
+
+ @Test
+ public void testUuidRoundTrip() {
+ Uuid hidl = new Uuid();
+ hidl.timeLow = 0xFEDCBA98;
+ hidl.timeMid = (short) 0xEDCB;
+ hidl.versionAndTimeHigh = (short) 0xDCBA;
+ hidl.variantAndClockSeqHigh = (short) 0xCBA9;
+ hidl.node = new byte[] { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
+
+ String aidl = ConversionUtil.hidl2aidlUuid(hidl);
+ assertEquals("fedcba98-edcb-dcba-cba9-111213141516", aidl);
+
+ Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
+ assertEquals(hidl, reconstructed);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
new file mode 100644
index 000000000000..82f32f88d3a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -0,0 +1,1306 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.audio.common.V2_0.AudioConfig;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
+import android.media.audio.common.AudioChannelMask;
+import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+import android.os.HwParcel;
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+@RunWith(Parameterized.class)
+public class SoundTriggerMiddlewareImplTest {
+ private static final String TAG = "SoundTriggerMiddlewareImplTest";
+
+ // We run the test once for every version of the underlying driver.
+ @Parameterized.Parameters
+ public static Object[] data() {
+ return new Object[]{
+ mock(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_2.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_3.ISoundTriggerHw.class),
+ };
+ }
+
+ @Mock
+ @Parameterized.Parameter
+ public android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
+
+ @Mock
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider = mock(
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
+
+ private SoundTriggerMiddlewareImpl mService;
+
+ private static ISoundTriggerCallback createCallbackMock() {
+ return mock(ISoundTriggerCallback.Stub.class, Mockito.CALLS_REAL_METHODS);
+ }
+
+ private static SoundModel createGenericSoundModel() {
+ return createSoundModel(SoundModelType.GENERIC);
+ }
+
+ private static SoundModel createSoundModel(int type) {
+ SoundModel model = new SoundModel();
+ model.type = type;
+ model.uuid = "12345678-2345-3456-4567-abcdef987654";
+ model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+ model.data = new byte[]{91, 92, 93, 94, 95};
+ return model;
+ }
+
+ private static PhraseSoundModel createPhraseSoundModel() {
+ PhraseSoundModel model = new PhraseSoundModel();
+ model.common = createSoundModel(SoundModelType.KEYPHRASE);
+ model.phrases = new Phrase[1];
+ model.phrases[0] = new Phrase();
+ model.phrases[0].id = 123;
+ model.phrases[0].users = new int[]{5, 6, 7};
+ model.phrases[0].locale = "locale";
+ model.phrases[0].text = "text";
+ model.phrases[0].recognitionModes =
+ RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+ return model;
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties(
+ boolean supportConcurrentCapture) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
+ properties.implementor = "implementor";
+ properties.description = "description";
+ properties.version = 123;
+ properties.uuid = new Uuid();
+ properties.uuid.timeLow = 1;
+ properties.uuid.timeMid = 2;
+ properties.uuid.versionAndTimeHigh = 3;
+ properties.uuid.variantAndClockSeqHigh = 4;
+ properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
+
+ properties.maxSoundModels = 456;
+ properties.maxKeyPhrases = 567;
+ properties.maxUsers = 678;
+ properties.recognitionModes = 789;
+ properties.captureTransition = true;
+ properties.maxBufferMs = 321;
+ properties.concurrentCapture = supportConcurrentCapture;
+ properties.triggerInEvent = true;
+ properties.powerConsumptionMw = 432;
+ return properties;
+ }
+
+ private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+ boolean supportConcurrentCapture) {
+ assertEquals("implementor", properties.implementor);
+ assertEquals("description", properties.description);
+ assertEquals(123, properties.version);
+ assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
+ assertEquals(456, properties.maxSoundModels);
+ assertEquals(567, properties.maxKeyPhrases);
+ assertEquals(678, properties.maxUsers);
+ assertEquals(789, properties.recognitionModes);
+ assertTrue(properties.captureTransition);
+ assertEquals(321, properties.maxBufferMs);
+ assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+ assertTrue(properties.triggerInEvent);
+ assertEquals(432, properties.powerConsumptionMw);
+ }
+
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.status = status;
+ halEvent.type = SoundModelType.GENERIC;
+ halEvent.model = hwHandle;
+ halEvent.captureAvailable = true;
+ // This field is ignored.
+ halEvent.captureSession = 123;
+ halEvent.captureDelayMs = 234;
+ halEvent.capturePreambleMs = 345;
+ halEvent.triggerInData = true;
+ halEvent.audioConfig = new AudioConfig();
+ halEvent.audioConfig.sampleRateHz = 456;
+ halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+ halEvent.audioConfig.format = AudioFormat.MP3;
+ // hwEvent.audioConfig.offloadInfo is irrelevant.
+ halEvent.data.add((byte) 31);
+ halEvent.data.add((byte) 32);
+ halEvent.data.add((byte) 33);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
+ halEvent.header.data.clear();
+ halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
+ return halEvent;
+ }
+
+ private static void validateRecognitionEvent(RecognitionEvent event, int status) {
+ assertEquals(status, event.status);
+ assertEquals(SoundModelType.GENERIC, event.type);
+ assertTrue(event.captureAvailable);
+ assertEquals(101, event.captureSession);
+ assertEquals(234, event.captureDelayMs);
+ assertEquals(345, event.capturePreambleMs);
+ assertTrue(event.triggerInData);
+ assertEquals(456, event.audioConfig.sampleRateHz);
+ assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
+ assertEquals(AudioFormat.MP3, event.audioConfig.format);
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_0(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event, int status) {
+ validateRecognitionEvent(event.common, status);
+
+ assertEquals(1, event.phraseExtras.length);
+ assertEquals(123, event.phraseExtras[0].id);
+ assertEquals(52, event.phraseExtras[0].confidenceLevel);
+ assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
+ event.phraseExtras[0].recognitionModes);
+ assertEquals(1, event.phraseExtras[0].levels.length);
+ assertEquals(31, event.phraseExtras[0].levels[0].userId);
+ assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
+ }
+
+ private void initService(boolean supportConcurrentCapture) throws RemoteException {
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ createDefaultProperties(
+ supportConcurrentCapture);
+ ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
+ 0)).onValues(0,
+ properties);
+ return null;
+ }).when(mHalDriver).getProperties(any());
+ mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+ }
+
+ private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ SoundModel model = createGenericSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
+
+ return handle;
+ }
+
+ private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+ SoundModel model = createGenericSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(driver).loadSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.header.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
+
+ return handle;
+ }
+
+ private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadGenericModel_2_1(module, hwHandle);
+ } else {
+ return loadGenericModel_2_0(module, hwHandle);
+ }
+ }
+
+ private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.common.data.toArray());
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(driver).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.header.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.header.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.common.data));
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadPhraseModel_2_1(module, hwHandle);
+ } else {
+ return loadPhraseModel_2_0(module, hwHandle);
+ }
+ }
+
+ private void unloadModel(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ module.unloadModel(handle);
+ verify(mHalDriver).unloadSoundModel(hwHandle);
+ verify(mAudioSessionProvider).releaseSession(101);
+ }
+
+ private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
+ callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(mHalDriver).startRecognition(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.captureRequested);
+ assertEquals(102, halConfig.captureHandle);
+ assertEquals(103, halConfig.captureDevice);
+ assertEquals(1, halConfig.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
+ callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_1(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.header.captureRequested);
+ assertEquals(102, halConfig.header.captureHandle);
+ assertEquals(103, halConfig.header.captureDevice);
+ assertEquals(1, halConfig.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.header.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+ HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return startRecognition_2_1(module, handle, hwHandle);
+ } else {
+ return startRecognition_2_0(module, handle, hwHandle);
+ }
+ }
+
+ private RecognitionConfig createRecognitionConfig() {
+ RecognitionConfig config = new RecognitionConfig();
+ config.captureRequested = true;
+ config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
+ config.phraseRecognitionExtras[0].id = 123;
+ config.phraseRecognitionExtras[0].confidenceLevel = 4;
+ config.phraseRecognitionExtras[0].recognitionModes = 5;
+ config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
+ config.phraseRecognitionExtras[0].levels[0].userId = 234;
+ config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
+ config.data = new byte[]{5, 4, 3, 2, 1};
+ return config;
+ }
+
+ private void stopRecognition(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ when(mHalDriver.stopRecognition(hwHandle)).thenReturn(0);
+ module.stopRecognition(handle);
+ verify(mHalDriver).stopRecognition(hwHandle);
+ }
+
+ private void verifyNotStartRecognition() throws RemoteException {
+ verify(mHalDriver, never()).startRecognition(anyInt(), any(), any(), anyInt());
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
+ never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+ }
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ clearInvocations(mHalDriver);
+ clearInvocations(mAudioSessionProvider);
+
+ // This binder is associated with the mock, so it can be cast to either version of the
+ // HAL interface.
+ final IHwBinder binder = new IHwBinder() {
+ @Override
+ public void transact(int code, HwParcel request, HwParcel reply, int flags)
+ throws RemoteException {
+ // This is a little hacky, but a very easy way to gracefully reject a request for
+ // an unsupported interface (after queryLocalInterface() returns null, the client
+ // will attempt a remote transaction to obtain the interface. RemoteException will
+ // cause it to give up).
+ throw new RemoteException();
+ }
+
+ @Override
+ public IHwInterface queryLocalInterface(String descriptor) {
+ if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
+ || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return mHalDriver;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ when(mHalDriver.asBinder()).thenReturn(binder);
+ }
+
+ @Test
+ public void testSetUpAndTearDown() {
+ }
+
+ @Test
+ public void testListModules() throws Exception {
+ initService(true);
+ // Note: input and output properties are NOT the same type, even though they are in any way
+ // equivalent. One is a type that's exposed by the HAL and one is a type that's exposed by
+ // the service. The service actually performs a (trivial) conversion between the two.
+ SoundTriggerModuleDescriptor[] allDescriptors = mService.listModules();
+ assertEquals(1, allDescriptors.length);
+
+ SoundTriggerModuleProperties properties = allDescriptors[0].properties;
+
+ validateDefaultProperties(properties, true);
+ }
+
+ @Test
+ public void testAttachDetach() throws Exception {
+ // Normal attachment / detachment.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachNotAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module not supporting concurrent
+ // capture.
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(false);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module supporting concurrent
+ // capture.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadPhraseModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 73;
+ int handle = loadPhraseModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 67;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForceRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForcePhraseRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortPhraseRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortPhraseRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onPhraseRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testParameterSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 12;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer((Answer<Void>) invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_3.ModelParameterRange range =
+ new android.hardware.soundtrigger.V2_3.ModelParameterRange();
+ range.start = 23;
+ range.end = 45;
+ OptionalModelParameterRange optionalRange = new OptionalModelParameterRange();
+ optionalRange.range(range);
+ resultCallback.onValues(0, optionalRange);
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(23, range.minInclusive);
+ assertEquals(45, range.maxInclusive);
+ }
+
+ @Test
+ public void testParameterNotSupportedOld() throws Exception {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return;
+ }
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testParameterNotSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, new OptionalModelParameterRange());
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testGetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 14;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, 234);
+ return null;
+ }).when(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ int value = module.getModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(234, value);
+ }
+
+ @Test
+ public void testSetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 17;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ when(driver.setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
+ 456)).thenReturn(0);
+
+ module.setModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR, 456);
+
+ verify(driver).setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, 456);
+ }
+
+ private static class SoundTriggerHwCallback {
+ private final android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback mCallback;
+ private final int mCookie;
+
+ SoundTriggerHwCallback(android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback,
+ int cookie) {
+ mCallback = callback;
+ mCookie = cookie;
+ }
+
+ private void sendRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).recognitionCallback_2_1(
+ createRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.recognitionCallback(createRecognitionEvent_2_0(hwHandle, status),
+ mCookie);
+ }
+ }
+
+ private void sendPhraseRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).phraseRecognitionCallback_2_1(
+ createPhraseRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.phraseRecognitionCallback(
+ createPhraseRecognitionEvent_2_0(hwHandle, status), mCookie);
+ }
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 0b4760d89686..a328568da7ed 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.spy;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -271,6 +272,18 @@ public class NotificationComparatorTest extends UiServiceTestCase {
}
@Test
+ public void testRankingScoreOverrides() {
+ NotificationComparator comp = new NotificationComparator(mContext);
+ NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+
+ when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
+ assertTrue(comp.compare(mRecordCheater, recordMinCallNonInterruptive) > 0);
+ assertTrue(comp.compare(mRecordColorizedCall, recordMinCallNonInterruptive) < 0);
+ }
+
+ @Test
public void testMessaging() {
NotificationComparator comp = new NotificationComparator(mContext);
assertTrue(comp.isImportantMessaging(mRecordInlineReply));
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
index c6203c5736fc..a39be565121f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
@@ -60,6 +60,7 @@ public class AnimatingActivityRegistryTest extends WindowTestsBase {
}
@Test
+ @FlakyTest(bugId = 144611135)
public void testDeferring() {
final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index 3008a75740e8..e0112809b22b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -88,6 +88,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
mFinishCallbackLatch.countDown();
}
+ @FlakyTest(bugId = 144611135)
@Test
public void testAnimation() throws Exception {
mSurfaceAnimationRunner
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 7174e5c8bb48..e17e0d87994c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -23,9 +23,20 @@ import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_APP_THE
import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -33,6 +44,7 @@ import com.google.android.collect.Sets;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/**
* Test class for {@link TaskSnapshotController}.
@@ -110,4 +122,57 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
assertEquals(SNAPSHOT_MODE_APP_THEME,
mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
}
+
+ @Test
+ public void testSnapshotBuilder() {
+ final GraphicBuffer buffer = Mockito.mock(GraphicBuffer.class);
+ final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
+ final long id = 1234L;
+ final ComponentName activityComponent = new ComponentName("package", ".Class");
+ final int windowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ final int systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN;
+ final int pixelFormat = PixelFormat.RGBA_8888;
+ final int orientation = Configuration.ORIENTATION_PORTRAIT;
+ final float scaleFraction = 0.25f;
+ final Rect contentInsets = new Rect(1, 2, 3, 4);
+
+ try {
+ ActivityManager.TaskSnapshot.Builder builder =
+ new ActivityManager.TaskSnapshot.Builder();
+ builder.setId(id);
+ builder.setTopActivityComponent(activityComponent);
+ builder.setSystemUiVisibility(systemUiVisibility);
+ builder.setWindowingMode(windowingMode);
+ builder.setColorSpace(sRGB);
+ builder.setReducedResolution(true);
+ builder.setOrientation(orientation);
+ builder.setContentInsets(contentInsets);
+ builder.setIsTranslucent(true);
+ builder.setScaleFraction(0.25f);
+ builder.setSnapshot(buffer);
+ builder.setIsRealSnapshot(true);
+ builder.setPixelFormat(pixelFormat);
+
+ // Not part of TaskSnapshot itself, used in screenshot process
+ assertEquals(pixelFormat, builder.getPixelFormat());
+
+ ActivityManager.TaskSnapshot snapshot = builder.build();
+ assertEquals(id, snapshot.getId());
+ assertEquals(activityComponent, snapshot.getTopActivityComponent());
+ assertEquals(systemUiVisibility, snapshot.getSystemUiVisibility());
+ assertEquals(windowingMode, snapshot.getWindowingMode());
+ assertEquals(sRGB, snapshot.getColorSpace());
+ assertTrue(snapshot.isReducedResolution());
+ assertEquals(orientation, snapshot.getOrientation());
+ assertEquals(contentInsets, snapshot.getContentInsets());
+ assertTrue(snapshot.isTranslucent());
+ assertEquals(scaleFraction, builder.getScaleFraction(), 0.001f);
+ assertSame(buffer, snapshot.getSnapshot());
+ assertTrue(snapshot.isRealSnapshot());
+ } finally {
+ if (buffer != null) {
+ buffer.destroy();
+ }
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 8ade4d27e495..0018c633e675 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -32,7 +32,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -82,7 +84,9 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.LinkedList;
+import java.util.List;
/**
* Tests for the {@link WindowState} class.
@@ -532,6 +536,31 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
+ public void testRequestDrawIfNeeded() {
+ final WindowState startingApp = createWindow(null /* parent */,
+ TYPE_BASE_APPLICATION, "startingApp");
+ final WindowState startingWindow = createWindow(null /* parent */,
+ TYPE_APPLICATION_STARTING, startingApp.mToken, "starting");
+ startingApp.mActivityRecord.startingWindow = startingWindow;
+ final WindowState keyguardHostWindow = mStatusBarWindow;
+ final WindowState allDrawnApp = mAppWindow;
+ allDrawnApp.mActivityRecord.allDrawn = true;
+
+ // The waiting list is used to ensure the content is ready when turning on screen.
+ final List<WindowState> outWaitingForDrawn = mDisplayContent.mWaitingForDrawn;
+ final List<WindowState> visibleWindows = Arrays.asList(mChildAppWindowAbove,
+ keyguardHostWindow, allDrawnApp, startingApp, startingWindow);
+ visibleWindows.forEach(w -> {
+ w.mHasSurface = true;
+ w.requestDrawIfNeeded(outWaitingForDrawn);
+ });
+
+ // Keyguard host window should be always contained. The drawn app or app with starting
+ // window are unnecessary to draw.
+ assertEquals(Arrays.asList(keyguardHostWindow, startingWindow), outWaitingForDrawn);
+ }
+
+ @Test
public void testGetTransformationMatrix() {
final int PARENT_WINDOW_OFFSET = 1;
final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2;
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 86016bb6036f..095e8e9b7b5b 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -75,12 +75,14 @@ class UsbSerialReader extends IUsbSerialReader.Stub {
if (uid != Process.SYSTEM_UID) {
enforcePackageBelongsToUid(uid, packageName);
+ UserHandle user = Binder.getCallingUserHandle();
int packageTargetSdkVersion;
long token = Binder.clearCallingIdentity();
try {
PackageInfo pkg;
try {
- pkg = mContext.getPackageManager().getPackageInfo(packageName, 0);
+ pkg = mContext.getPackageManager()
+ .getPackageInfoAsUser(packageName, 0, user.getIdentifier());
} catch (PackageManager.NameNotFoundException e) {
throw new RemoteException("package " + packageName + " cannot be found");
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 6132cc2250b9..404346fa9286 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1291,6 +1291,7 @@ public class VoiceInteractionManagerService extends SystemService {
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserUnlocked: " + mCurUserUnlocked);
pw.println(" mCurUserSupported: " + mCurUserSupported);
+ dumpSupportedUsers(pw, " ");
if (mImpl == null) {
pw.println(" (No active implementation)");
return;
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 8b8c86be7b0a..ea641f866b98 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -19,7 +19,7 @@ package android.telecom;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 86ad795b9ea2..a8852a849604 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -20,12 +20,11 @@ import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import com.android.internal.telecom.IVideoProvider;
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index a5d25e2ce4bb..fb6f99405759 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -17,7 +17,7 @@
package android.telecom;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -41,8 +41,8 @@ import com.android.i18n.phonenumbers.NumberParseException;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
-
import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Locale;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index c06327995bc0..2d0b49dbe30c 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,9 +21,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Notification;
import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -695,6 +695,15 @@ public abstract class Connection extends Conferenceable {
public static final String EVENT_CALL_HOLD_FAILED = "android.telecom.event.CALL_HOLD_FAILED";
/**
+ * Connection event used to inform Telecom when a switch operation on a call has failed.
+ * <p>
+ * Sent via {@link #sendConnectionEvent(String, Bundle)}. The {@link Bundle} parameter is
+ * expected to be null when this connection event is used.
+ */
+ public static final String EVENT_CALL_SWITCH_FAILED =
+ "android.telecom.event.CALL_SWITCH_FAILED";
+
+ /**
* Connection event used to inform {@link InCallService}s when the process of merging a
* Connection into a conference has begun.
* <p>
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 7d4ee7686512..4f6a9d6450f8 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -16,7 +16,7 @@
package android.telecom;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index a234bb0af8fa..be4e2f4c65e1 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,22 +16,21 @@
package android.telecom;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.telecom.Call.Details.CallDirection;
+import com.android.internal.telecom.IVideoProvider;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import com.android.internal.telecom.IVideoProvider;
-
/**
* Information about a call that is used between InCallService and Telecom.
* @hide
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 61a639a1a235..a427ed612b31 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,8 +17,8 @@
package android.telecom;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index eb568e04ebf3..e1bcb5fbdf00 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -18,7 +18,7 @@ package android.telecom;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Build;
import android.os.Parcel;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 39d741c3d3bf..9cf4803966c6 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -26,7 +26,7 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -485,6 +485,103 @@ public class TelecomManager {
"android.telecom.extra.START_CALL_WITH_RTT";
/**
+ * Start an activity indicating that the completion of an outgoing call or an incoming call
+ * which was not blocked by the {@link CallScreeningService}, and which was NOT terminated
+ * while the call was in {@link Call#STATE_AUDIO_PROCESSING}.
+ *
+ * The {@link Uri} extra {@link #EXTRA_HANDLE} will contain the uri handle(phone number) for the
+ * call which completed.
+ *
+ * The integer extra {@link #EXTRA_DISCONNECT_CAUSE} will indicate the reason for the call
+ * disconnection. See {@link #EXTRA_DISCONNECT_CAUSE} for more information.
+ *
+ * The integer extra {@link #EXTRA_CALL_DURATION} will indicate the duration of the call. See
+ * {@link #EXTRA_CALL_DURATION} for more information.
+ */
+ public static final String ACTION_POST_CALL = "android.telecom.action.POST_CALL";
+
+ /**
+ * A {@link Uri} extra, which when set on the {@link #ACTION_POST_CALL} intent, indicates the
+ * uri handle(phone number) of the completed call.
+ */
+ public static final String EXTRA_HANDLE = "android.telecom.extra.HANDLE";
+
+ /**
+ * A integer value provided for completed calls to indicate the reason for the call
+ * disconnection.
+ * <p>
+ * Allowed values:
+ * <ul>
+ * <li>{@link DisconnectCause#UNKNOWN}</li>
+ * <li>{@link DisconnectCause#LOCAL}</li>
+ * <li>{@link DisconnectCause#REMOTE}</li>
+ * <li>{@link DisconnectCause#REJECTED}</li>
+ * <li>{@link DisconnectCause#MISSED}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String EXTRA_DISCONNECT_CAUSE = "android.telecom.extra.DISCONNECT_CAUSE";
+
+ /**
+ * A integer value provided for completed calls to indicate the duration of the call.
+ * <p>
+ * Allowed values:
+ * <ul>
+ * <li>{@link #DURATION_VERY_SHORT}</li>
+ * <li>{@link #DURATION_SHORT}</li>
+ * <li>{@link #DURATION_MEDIUM}</li>
+ * <li>{@link #DURATION_LONG}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was < 3 seconds.
+ */
+ public static final int DURATION_VERY_SHORT = 0;
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was >= 3 seconds and < 60 seconds.
+ */
+ public static final int DURATION_SHORT = 1;
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was >= 60 seconds and < 120 seconds.
+ */
+ public static final int DURATION_MEDIUM = 2;
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was >= 120 seconds.
+ */
+ public static final int DURATION_LONG = 3;
+
+ /**
+ * The threshold between {@link #DURATION_VERY_SHORT} calls and {@link #DURATION_SHORT} calls in
+ * milliseconds.
+ * @hide
+ */
+ public static final long VERY_SHORT_CALL_TIME_MS = 3000;
+
+ /**
+ * The threshold between {@link #DURATION_SHORT} calls and {@link #DURATION_MEDIUM} calls in
+ * milliseconds.
+ * @hide
+ */
+ public static final long SHORT_CALL_TIME_MS = 60000;
+
+ /**
+ * The threshold between {@link #DURATION_MEDIUM} calls and {@link #DURATION_LONG} calls in
+ * milliseconds.
+ * @hide
+ */
+ public static final long MEDIUM_CALL_TIME_MS = 120000;
+
+ /**
* A boolean meta-data value indicating whether an {@link InCallService} implements an
* in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which
* would also like to replace the in-call interface should set this meta-data to {@code true} in
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 4a1aa0a8ffa4..109e7f829f2e 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -16,7 +16,7 @@
package android.telecom;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index 64e6ca3416e3..4197f3cfc6c3 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -19,7 +19,6 @@ package android.telecom;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index 5fb4e90b9666..22cbdaa0f133 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Build;
import android.telephony.Rlog;
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 70ce1bfc4bff..967148890fde 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -20,6 +20,7 @@ import android.Manifest.permission;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.role.RoleManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -47,8 +48,6 @@ import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
diff --git a/telephony/common/com/android/internal/telephony/SmsConstants.java b/telephony/common/com/android/internal/telephony/SmsConstants.java
index 19f52b0ef429..3aa8bbf607d1 100644
--- a/telephony/common/com/android/internal/telephony/SmsConstants.java
+++ b/telephony/common/com/android/internal/telephony/SmsConstants.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* SMS Constants and must be the same as the corresponding
diff --git a/telephony/common/com/google/android/mms/ContentType.java b/telephony/common/com/google/android/mms/ContentType.java
index 12e4b7e26e1e..4a971dd34c8f 100644
--- a/telephony/common/com/google/android/mms/ContentType.java
+++ b/telephony/common/com/google/android/mms/ContentType.java
@@ -17,7 +17,7 @@
package com.google.android.mms;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.ArrayList;
diff --git a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java
index 2836c3075b3b..55087ff0fb1d 100644
--- a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java
+++ b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java
@@ -17,7 +17,7 @@
package com.google.android.mms;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Thrown when an invalid header value was set.
diff --git a/telephony/common/com/google/android/mms/MmsException.java b/telephony/common/com/google/android/mms/MmsException.java
index 5be33ed1fac9..24bceb37f590 100644
--- a/telephony/common/com/google/android/mms/MmsException.java
+++ b/telephony/common/com/google/android/mms/MmsException.java
@@ -17,7 +17,7 @@
package com.google.android.mms;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A generic exception that is thrown by the Mms client.
diff --git a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java
index ae447d7a7417..8693385bb032 100644
--- a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java
+++ b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/Base64.java b/telephony/common/com/google/android/mms/pdu/Base64.java
index 483fa7f9842e..0d6a46a59fcc 100644
--- a/telephony/common/com/google/android/mms/pdu/Base64.java
+++ b/telephony/common/com/google/android/mms/pdu/Base64.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class Base64 {
/**
diff --git a/telephony/common/com/google/android/mms/pdu/CharacterSets.java b/telephony/common/com/google/android/mms/pdu/CharacterSets.java
index 27da35e2d928..5172b7b67f88 100644
--- a/telephony/common/com/google/android/mms/pdu/CharacterSets.java
+++ b/telephony/common/com/google/android/mms/pdu/CharacterSets.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
diff --git a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java
index 7093ac63338c..8fb6a7545abf 100644
--- a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java
+++ b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
index 41662750842f..8c0380f77cdd 100644
--- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
@@ -17,10 +17,9 @@
package com.google.android.mms.pdu;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
diff --git a/telephony/common/com/google/android/mms/pdu/GenericPdu.java b/telephony/common/com/google/android/mms/pdu/GenericPdu.java
index ebf16ac7e632..320b13ffed2b 100644
--- a/telephony/common/com/google/android/mms/pdu/GenericPdu.java
+++ b/telephony/common/com/google/android/mms/pdu/GenericPdu.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java
index e108f7600baf..42a89c69e873 100644
--- a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java
+++ b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/NotificationInd.java b/telephony/common/com/google/android/mms/pdu/NotificationInd.java
index b561bd4ab3a7..ca4615c2e9fe 100644
--- a/telephony/common/com/google/android/mms/pdu/NotificationInd.java
+++ b/telephony/common/com/google/android/mms/pdu/NotificationInd.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java
index 3c70f86a0890..ebd81afc0173 100644
--- a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java
+++ b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/PduBody.java b/telephony/common/com/google/android/mms/pdu/PduBody.java
index 51914e4110b0..f7f285f653b9 100644
--- a/telephony/common/com/google/android/mms/pdu/PduBody.java
+++ b/telephony/common/com/google/android/mms/pdu/PduBody.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.HashMap;
import java.util.Map;
diff --git a/telephony/common/com/google/android/mms/pdu/PduComposer.java b/telephony/common/com/google/android/mms/pdu/PduComposer.java
index e24bf21a11b5..b8b212c493aa 100644
--- a/telephony/common/com/google/android/mms/pdu/PduComposer.java
+++ b/telephony/common/com/google/android/mms/pdu/PduComposer.java
@@ -17,12 +17,11 @@
package com.google.android.mms.pdu;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.text.TextUtils;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
diff --git a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java
index 8551b2f9b693..57141fedf1e0 100644
--- a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java
+++ b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class PduContentTypes {
/**
diff --git a/telephony/common/com/google/android/mms/pdu/PduHeaders.java b/telephony/common/com/google/android/mms/pdu/PduHeaders.java
index b5244645fda1..3e6218480dc5 100644
--- a/telephony/common/com/google/android/mms/pdu/PduHeaders.java
+++ b/telephony/common/com/google/android/mms/pdu/PduHeaders.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/PduParser.java b/telephony/common/com/google/android/mms/pdu/PduParser.java
index f48399410723..5340245ae869 100755
--- a/telephony/common/com/google/android/mms/pdu/PduParser.java
+++ b/telephony/common/com/google/android/mms/pdu/PduParser.java
@@ -17,10 +17,9 @@
package com.google.android.mms.pdu;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import com.google.android.mms.ContentType;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/PduPart.java b/telephony/common/com/google/android/mms/pdu/PduPart.java
index 09b775118dc3..8dd976b2569f 100644
--- a/telephony/common/com/google/android/mms/pdu/PduPart.java
+++ b/telephony/common/com/google/android/mms/pdu/PduPart.java
@@ -17,10 +17,9 @@
package com.google.android.mms.pdu;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.HashMap;
import java.util.Map;
diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java
index b237705274da..229592806dec 100755
--- a/telephony/common/com/google/android/mms/pdu/PduPersister.java
+++ b/telephony/common/com/google/android/mms/pdu/PduPersister.java
@@ -17,6 +17,7 @@
package com.google.android.mms.pdu;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -39,8 +40,6 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import com.google.android.mms.ContentType;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.MmsException;
diff --git a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java
index 9d6535c72e90..4e1d7f5775ec 100644
--- a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java
+++ b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.ByteArrayOutputStream;
diff --git a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java
index e38c62dde622..4ba3c71580e0 100644
--- a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java
+++ b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java
index 9696bc259d00..37ccfb9c9b9b 100644
--- a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java
+++ b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java
index 03755af4189c..260adfc093f2 100644
--- a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java
+++ b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/SendConf.java b/telephony/common/com/google/android/mms/pdu/SendConf.java
index b85982791ada..779923801bfa 100644
--- a/telephony/common/com/google/android/mms/pdu/SendConf.java
+++ b/telephony/common/com/google/android/mms/pdu/SendConf.java
@@ -17,7 +17,7 @@
package com.google.android.mms.pdu;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.InvalidHeaderValueException;
diff --git a/telephony/common/com/google/android/mms/pdu/SendReq.java b/telephony/common/com/google/android/mms/pdu/SendReq.java
index c1b7f934c0f7..6e2f2da01791 100644
--- a/telephony/common/com/google/android/mms/pdu/SendReq.java
+++ b/telephony/common/com/google/android/mms/pdu/SendReq.java
@@ -17,10 +17,9 @@
package com.google.android.mms.pdu;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import com.google.android.mms.InvalidHeaderValueException;
public class SendReq extends MultimediaMessagePdu {
diff --git a/telephony/common/com/google/android/mms/util/AbstractCache.java b/telephony/common/com/google/android/mms/util/AbstractCache.java
index ab5d48a4ce3d..25862e73581e 100644
--- a/telephony/common/com/google/android/mms/util/AbstractCache.java
+++ b/telephony/common/com/google/android/mms/util/AbstractCache.java
@@ -17,10 +17,9 @@
package com.google.android.mms.util;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.HashMap;
public abstract class AbstractCache<K, V> {
diff --git a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java
index 118de465a518..0f9390daa725 100644
--- a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java
+++ b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java
@@ -17,12 +17,11 @@
package com.google.android.mms.util;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.drm.DrmManagerClient;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
public class DownloadDrmHelper {
private static final String TAG = "DownloadDrmHelper";
diff --git a/telephony/common/com/google/android/mms/util/DrmConvertSession.java b/telephony/common/com/google/android/mms/util/DrmConvertSession.java
index 0e8ec91f4ef6..156c7ad8baac 100644
--- a/telephony/common/com/google/android/mms/util/DrmConvertSession.java
+++ b/telephony/common/com/google/android/mms/util/DrmConvertSession.java
@@ -16,14 +16,13 @@
*/
package com.google.android.mms.util;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.drm.DrmConvertedStatus;
import android.drm.DrmManagerClient;
import android.provider.Downloads;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
diff --git a/telephony/common/com/google/android/mms/util/PduCache.java b/telephony/common/com/google/android/mms/util/PduCache.java
index 94e38946f632..c380d6b3e30f 100644
--- a/telephony/common/com/google/android/mms/util/PduCache.java
+++ b/telephony/common/com/google/android/mms/util/PduCache.java
@@ -17,14 +17,13 @@
package com.google.android.mms.util;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentUris;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.Telephony.Mms;
import android.util.Log;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.HashMap;
import java.util.HashSet;
diff --git a/telephony/common/com/google/android/mms/util/PduCacheEntry.java b/telephony/common/com/google/android/mms/util/PduCacheEntry.java
index 1ecd1bf93e7f..a4a25d2471ff 100644
--- a/telephony/common/com/google/android/mms/util/PduCacheEntry.java
+++ b/telephony/common/com/google/android/mms/util/PduCacheEntry.java
@@ -17,7 +17,7 @@
package com.google.android.mms.util;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.google.android.mms.pdu.GenericPdu;
diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
index 2dd1dc11c2a9..31fe4d7683d6 100644
--- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java
+++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
@@ -18,6 +18,7 @@
package com.google.android.mms.util;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -27,8 +28,6 @@ import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
public final class SqliteWrapper {
private static final String TAG = "SqliteWrapper";
private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 6c357ccdd03d..8450a9018634 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -19,7 +19,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.carrier.CarrierIdentifier;
diff --git a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
index c7a985160730..2382f657c9ee 100644
--- a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
+++ b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
@@ -17,7 +17,7 @@ package android.service.euicc;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
index abd4065c754a..d0fb51180c1d 100644
--- a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
+++ b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
@@ -17,7 +17,7 @@ package android.service.euicc;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 001888c6c117..b08650695807 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -24,7 +24,7 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.os.PersistableBundle;
@@ -419,12 +419,33 @@ public class CarrierConfigManager {
KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
/**
- * Override the device's configuration for the ImsService to use for this SIM card.
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support both IMS MMTEL and RCS feature functionality instead of the device default
+ * ImsService for this subscription.
+ * @deprecated Use {@link #KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING} and
+ * {@link #KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING} instead to configure these values
+ * separately. If any of those values are not empty, they will override this value.
*/
public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
"config_ims_package_override_string";
/**
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support IMS MMTEL feature functionality instead of the device default ImsService for this
+ * subscription.
+ */
+ public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING =
+ "config_ims_mmtel_package_override_string";
+
+ /**
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support IMS RCS feature functionality instead of the device default ImsService for this
+ * subscription.
+ */
+ public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING =
+ "config_ims_rcs_package_override_string";
+
+ /**
* Override the package that will manage {@link SubscriptionPlan}
* information instead of the {@link CarrierService} that defines this
* value.
@@ -1946,6 +1967,12 @@ public class CarrierConfigManager {
"allow_add_call_during_video_call";
/**
+ * When false, indicates that holding a video call is disabled
+ */
+ public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL =
+ "allow_holding_video_call";
+
+ /**
* When true, indicates that the HD audio icon in the in-call screen should not be shown for
* VoWifi calls.
* @hide
@@ -2176,7 +2203,7 @@ public class CarrierConfigManager {
* the start of the next month.
* <p>
* This setting may be still overridden by explicit user choice. By default,
- * the platform value will be used.
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
"monthly_data_cycle_day_int";
@@ -2185,10 +2212,7 @@ public class CarrierConfigManager {
* When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG},
* or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default
* value will be used for that key.
- *
- * @hide
*/
- @Deprecated
public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
/**
@@ -2212,8 +2236,8 @@ public class CarrierConfigManager {
* If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will
* be disabled.
* <p>
- * This setting may be overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be overridden by explicit user choice. By default,
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
"data_warning_threshold_bytes_long";
@@ -2221,8 +2245,7 @@ public class CarrierConfigManager {
/**
* Controls if the device should automatically notify the user as they reach
* their cellular data warning. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL =
"data_warning_notification_bool";
@@ -2244,8 +2267,8 @@ public class CarrierConfigManager {
* phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be
* disabled.
* <p>
- * This setting may be overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be overridden by explicit user choice. By default,
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
"data_limit_threshold_bytes_long";
@@ -2253,8 +2276,7 @@ public class CarrierConfigManager {
/**
* Controls if the device should automatically notify the user as they reach
* their cellular data limit. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL =
"data_limit_notification_bool";
@@ -2262,8 +2284,7 @@ public class CarrierConfigManager {
/**
* Controls if the device should automatically notify the user when rapid
* cellular data usage is observed. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL =
"data_rapid_notification_bool";
@@ -3486,6 +3507,8 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+ sDefaults.putString(KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING, null);
+ sDefaults.putString(KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
@@ -3629,6 +3652,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true);
sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false);
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index d4d4e1374477..7314edda8f16 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -28,10 +28,15 @@ import java.util.stream.Collectors;
/**
- * This utils class is specifically used for geo-targeting of CellBroadcast messages.
+ * This utils class is used for geo-fencing of CellBroadcast messages and is used by the cell
+ * broadcast module.
+ *
* The coordinates used by this utils class are latitude and longitude, but some algorithms in this
* class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use
* this class for anything other then geo-targeting of cellbroadcast messages.
+ *
+ * More information regarding cell broadcast geo-fencing logic is laid out in 3GPP TS 23.041 and
+ * ATIS-0700041.
* @hide
*/
@SystemApi
@@ -82,7 +87,7 @@ public class CbGeoUtils {
/** @hide */
private static final String POLYGON_SYMBOL = "polygon";
- /** Point represent by (latitude, longitude). */
+ /** A point represented by (latitude, longitude). */
public static class LatLng {
public final double lat;
public final double lng;
@@ -98,8 +103,8 @@ public class CbGeoUtils {
}
/**
- * @param p the point use to calculate the subtraction result.
- * @return the result of this point subtract the given point {@code p}.
+ * @param p the point to subtract
+ * @return the result of the subtraction
*/
@NonNull
public LatLng subtract(@NonNull LatLng p) {
@@ -107,9 +112,9 @@ public class CbGeoUtils {
}
/**
- * Calculate the distance in meter between this point and the given point {@code p}.
- * @param p the point use to calculate the distance.
- * @return the distance in meter.
+ * Calculate the distance in meters between this point and the given point {@code p}.
+ * @param p the point used to calculate the distance.
+ * @return the distance in meters.
*/
public double distance(@NonNull LatLng p) {
double dlat = Math.sin(0.5 * Math.toRadians(lat - p.lat));
@@ -126,8 +131,9 @@ public class CbGeoUtils {
}
/**
- * The class represents a simple polygon with at least 3 points.
- * @hide
+ * A class representing a simple polygon with at least 3 points. This is used for geo-fencing
+ * logic with cell broadcasts. More information regarding cell broadcast geo-fencing logic is
+ * laid out in 3GPP TS 23.041 and ATIS-0700041.
*/
public static class Polygon implements Geometry {
/**
@@ -146,7 +152,7 @@ public class CbGeoUtils {
* connected to form an edge of the polygon. The polygon has at least 3 vertices, and the
* last vertices and the first vertices must be adjacent.
*
- * The longitude difference in the vertices should be less than 180 degree.
+ * The longitude difference in the vertices should be less than 180 degrees.
*/
public Polygon(@NonNull List<LatLng> vertices) {
mVertices = vertices;
@@ -165,19 +171,24 @@ public class CbGeoUtils {
.collect(Collectors.toList());
}
- public List<LatLng> getVertices() {
+ /**
+ * Return the list of vertices which compose the polygon.
+ */
+ public @NonNull List<LatLng> getVertices() {
return mVertices;
}
/**
- * Check if the given point {@code p} is inside the polygon. This method counts the number
- * of times the polygon winds around the point P, A.K.A "winding number". The point is
- * outside only when this "winding number" is 0.
+ * Check if the given LatLng is inside the polygon.
*
- * If a point is on the edge of the polygon, it is also considered to be inside the polygon.
+ * If a LatLng is on the edge of the polygon, it is also considered to be inside the
+ * polygon.
*/
@Override
- public boolean contains(LatLng latLng) {
+ public boolean contains(@NonNull LatLng latLng) {
+ // This method counts the number of times the polygon winds around the point P, A.K.A
+ // "winding number". The point is outside only when this "winding number" is 0.
+
Point p = convertAndScaleLatLng(latLng);
int n = mScaledVertices.size();
@@ -246,6 +257,7 @@ public class CbGeoUtils {
return a.x * b.y - a.y * b.x;
}
+ /** @hide */
static final class Point {
public final double x;
public final double y;
@@ -271,29 +283,47 @@ public class CbGeoUtils {
}
/**
- * The class represents a circle.
- * @hide
+ * A class represents a {@link Geometry} in the shape of a Circle. This is used for handling
+ * geo-fenced cell broadcasts. More information regarding cell broadcast geo-fencing logic is
+ * laid out in 3GPP TS 23.041 and ATIS-0700041.
*/
public static class Circle implements Geometry {
private final LatLng mCenter;
private final double mRadiusMeter;
- public Circle(LatLng center, double radiusMeter) {
+ /**
+ * Construct a Circle given a center point and a radius in meters.
+ *
+ * @param center the latitude and longitude of the center of the circle
+ * @param radiusInMeters the radius of the circle in meters
+ */
+ public Circle(@NonNull LatLng center, double radiusInMeters) {
this.mCenter = center;
- this.mRadiusMeter = radiusMeter;
+ this.mRadiusMeter = radiusInMeters;
}
- public LatLng getCenter() {
+ /**
+ * Return the latitude and longitude of the center of the circle;
+ */
+ public @NonNull LatLng getCenter() {
return mCenter;
}
+ /**
+ * Return the radius of the circle in meters.
+ */
public double getRadius() {
return mRadiusMeter;
}
+ /**
+ * Check if the given LatLng is inside the circle.
+ *
+ * If a LatLng is on the edge of the circle, it is also considered to be inside the circle.
+ */
@Override
- public boolean contains(LatLng p) {
- return mCenter.distance(p) <= mRadiusMeter;
+ public boolean contains(@NonNull LatLng latLng) {
+ return mCenter.distance(latLng) <= mRadiusMeter;
}
@Override
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 25c6577bdcf5..f0c0ddebbfd1 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -17,7 +17,7 @@
package android.telephony;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 997b19f3d4eb..9bd33c17c048 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -17,7 +17,7 @@
package android.telephony;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index 16fbae25199b..aebe78031ba2 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -205,10 +205,10 @@ public final class ModemActivityInfo implements Parcelable {
}
/**
+ * Indicate if the ModemActivityInfo is invalid due to modem's invalid reporting.
+ *
* @return {@code true} if this {@link ModemActivityInfo} record is valid,
* {@code false} otherwise.
- *
- * @hide
*/
public boolean isValid() {
for (TransmitPower powerInfo : getTransmitPowerInfo()) {
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 257d634f1577..78ad5c58423c 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -19,8 +19,10 @@ package android.telephony;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.net.LinkProperties;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.ApnType;
@@ -29,6 +31,8 @@ import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.data.ApnSetting;
+import dalvik.system.VMRuntime;
+
import java.util.Objects;
@@ -46,35 +50,62 @@ import java.util.Objects;
* <li>Data connection fail cause.
* </ul>
*
- * @hide
*/
-@SystemApi
public final class PreciseDataConnectionState implements Parcelable {
private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
private @DataFailureCause int mFailCause = DataFailCause.NONE;
- private @ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
- private String mAPN = "";
+ private @ApnType int mApnTypes = ApnSetting.TYPE_NONE;
+ private String mApn = "";
private LinkProperties mLinkProperties = null;
+ private ApnSetting mApnSetting = null;
/**
* Constructor
*
+ * @deprecated this constructor has been superseded and should not be used.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @Deprecated
+ @UnsupportedAppUsage // (maxTargetSdk = Build.VERSION_CODES.Q)
+ // FIXME: figure out how to remove the UnsupportedAppUsage and delete this constructor
public PreciseDataConnectionState(@DataState int state,
@NetworkType int networkType,
- @ApnType int apnTypes, String apn,
- LinkProperties linkProperties,
+ @ApnType int apnTypes, @NonNull String apn,
+ @Nullable LinkProperties linkProperties,
@DataFailureCause int failCause) {
+ this(state, networkType, apnTypes, apn, linkProperties, failCause, null);
+ }
+
+
+ /**
+ * Constructor
+ *
+ * @param state the state of the data connection
+ * @param networkType the access network that is/would carry this data connection
+ * @param apnTypes the APN types that this data connection carries
+ * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
+ * if there is no valid APN setting for the specific type, then this will be null
+ * @param linkProperties if the data connection is connected, the properties of the connection
+ * @param failCause in case a procedure related to this data connection fails, a non-zero error
+ * code indicating the cause of the failure.
+ * @hide
+ */
+ public PreciseDataConnectionState(@DataState int state,
+ @NetworkType int networkType,
+ @ApnType int apnTypes, @NonNull String apn,
+ @Nullable LinkProperties linkProperties,
+ @DataFailureCause int failCause,
+ @Nullable ApnSetting apnSetting) {
mState = state;
mNetworkType = networkType;
- mAPNTypes = apnTypes;
- mAPN = apn;
+ mApnTypes = apnTypes;
+ mApn = apn;
mLinkProperties = linkProperties;
mFailCause = failCause;
+ mApnSetting = apnSetting;
}
/**
@@ -93,76 +124,160 @@ public final class PreciseDataConnectionState implements Parcelable {
private PreciseDataConnectionState(Parcel in) {
mState = in.readInt();
mNetworkType = in.readInt();
- mAPNTypes = in.readInt();
- mAPN = in.readString();
- mLinkProperties = (LinkProperties)in.readParcelable(null);
+ mApnTypes = in.readInt();
+ mApn = in.readString();
+ mLinkProperties = (LinkProperties) in.readParcelable(null);
mFailCause = in.readInt();
+ mApnSetting = (ApnSetting) in.readParcelable(null);
}
/**
* Returns the state of data connection that supported the apn types returned by
* {@link #getDataConnectionApnTypeBitMask()}
+ *
+ * @deprecated use {@link #getState()}
+ * @hide
*/
+ @Deprecated
+ @SystemApi
public @DataState int getDataConnectionState() {
+ if (mState == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ return TelephonyManager.DATA_CONNECTED;
+ }
+
+ return mState;
+ }
+
+ /**
+ * Returns the high-level state of this data connection.
+ */
+ public @DataState int getState() {
return mState;
}
/**
* Returns the network type associated with this data connection.
+ *
+ * @deprecated use {@link getNetworkType()}
* @hide
*/
+ @Deprecated
+ @SystemApi
public @NetworkType int getDataConnectionNetworkType() {
return mNetworkType;
}
/**
- * Returns the data connection APN types supported by this connection and triggers
- * {@link PreciseDataConnectionState} change.
+ * Returns the network type associated with this data connection.
+ *
+ * Return the current/latest (radio) bearer technology that carries this data connection.
+ * For a variety of reasons, the network type can change during the life of the data
+ * connection, and this information is not reliable unless the physical link is currently
+ * active; (there is currently no mechanism to know whether the physical link is active at
+ * any given moment). Thus, this value is generally correct but may not be relied-upon to
+ * represent the status of the radio bearer at any given moment.
+ */
+ public @NetworkType int getNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * Returns the APN types mapped to this data connection.
+ *
+ * @deprecated use {@link #getApnSetting()}
+ * @hide
*/
+ @Deprecated
+ @SystemApi
public @ApnType int getDataConnectionApnTypeBitMask() {
- return mAPNTypes;
+ return mApnTypes;
}
/**
- * Returns APN {@link ApnSetting} of this data connection.
+ * Returns APN of this data connection.
+ *
+ * @deprecated use {@link #getApnSetting()}
+ * @hide
*/
- @Nullable
+ @NonNull
+ @SystemApi
+ @Deprecated
public String getDataConnectionApn() {
- return mAPN;
+ return mApn;
}
/**
* Get the properties of the network link {@link LinkProperties}.
+ *
+ * @deprecated use {@link #getLinkProperties()}
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @SystemApi
+ @Nullable
public LinkProperties getDataConnectionLinkProperties() {
return mLinkProperties;
}
/**
- * Returns data connection fail cause, in case there was a failure.
+ * Get the properties of the network link {@link LinkProperties}.
+ */
+ @Nullable
+ public LinkProperties getLinkProperties() {
+ return mLinkProperties;
+ }
+
+ /**
+ * Returns the cause code generated by the most recent state change.
+ *
+ * @deprecated use {@link #getLastCauseCode()}
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public int getDataConnectionFailCause() {
+ return mFailCause;
+ }
+
+ /**
+ * Returns the cause code generated by the most recent state change.
+ *
+ * Return the cause code for the most recent change in {@link #getState}. In the event of an
+ * error, this cause code will be non-zero.
*/
- public @Annotation.DataFailureCause int getDataConnectionFailCause() {
+ // FIXME(b144774287): some of these cause codes should have a prescribed meaning.
+ public int getLastCauseCode() {
return mFailCause;
}
+ /**
+ * Return the APN Settings for this data connection.
+ *
+ * Returns the ApnSetting that was used to configure this data connection.
+ */
+ // FIXME: This shouldn't be nullable; update once the ApnSetting is supplied correctly
+ @Nullable ApnSetting getApnSetting() {
+ return mApnSetting;
+ }
+
@Override
public int describeContents() {
return 0;
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(mState);
out.writeInt(mNetworkType);
- out.writeInt(mAPNTypes);
- out.writeString(mAPN);
+ out.writeInt(mApnTypes);
+ out.writeString(mApn);
out.writeParcelable(mLinkProperties, flags);
out.writeInt(mFailCause);
+ out.writeParcelable(mApnSetting, flags);
}
- public static final @android.annotation.NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
+ public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
= new Parcelable.Creator<PreciseDataConnectionState>() {
public PreciseDataConnectionState createFromParcel(Parcel in) {
@@ -176,8 +291,8 @@ public final class PreciseDataConnectionState implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
- mFailCause);
+ return Objects.hash(mState, mNetworkType, mApnTypes, mApn, mLinkProperties,
+ mFailCause, mApnSetting);
}
@Override
@@ -188,11 +303,12 @@ public final class PreciseDataConnectionState implements Parcelable {
}
PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
- return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+ return Objects.equals(mApn, other.mApn) && mApnTypes == other.mApnTypes
&& mFailCause == other.mFailCause
&& Objects.equals(mLinkProperties, other.mLinkProperties)
&& mNetworkType == other.mNetworkType
- && mState == other.mState;
+ && mState == other.mState
+ && Objects.equals(mApnSetting, other.mApnSetting);
}
@NonNull
@@ -202,10 +318,11 @@ public final class PreciseDataConnectionState implements Parcelable {
sb.append("Data Connection state: " + mState);
sb.append(", Network type: " + mNetworkType);
- sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
- sb.append(", APN: " + mAPN);
+ sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mApnTypes));
+ sb.append(", APN: " + mApn);
sb.append(", Link properties: " + mLinkProperties);
sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
+ sb.append(", Apn Setting: " + mApnSetting);
return sb.toString();
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 37f33885de20..cab5286f53b7 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1998,6 +1998,27 @@ public final class SmsManager {
}
}
+ /**
+ * Gets the total capacity of SMS storage on RUIM and SIM cards
+ *
+ * @return the total capacity count of SMS on RUIM and SIM cards
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getSmsCapacityOnIcc() {
+ int ret = 0;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ //ignore it
+ }
+ return ret;
+ }
+
// see SmsMessage.getStatusOnIcc
/** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b1dd2d8e3f49..2082795b76e0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -757,8 +757,10 @@ public class TelephonyManager {
* Retrieve with
* {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
*
+ * @deprecated Should use the {@link TelecomManager#EXTRA_DISCONNECT_CAUSE} instead.
* @hide
*/
+ @Deprecated
public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause";
/**
@@ -801,18 +803,6 @@ public class TelephonyManager {
public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
/**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an String representation of the data interface.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getParcelableExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY;
-
- /**
* Broadcast intent action for letting the default dialer to know to show voicemail
* notification.
*
@@ -1473,6 +1463,26 @@ public class TelephonyManager {
*/
public static final String EXTRA_SIM_COMBINATION_NAMES =
"android.telephony.extra.SIM_COMBINATION_NAMES";
+
+ /**
+ * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
+ * This is a sticky broadcast.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li><em>time</em> - The time as a long in UTC milliseconds.</li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
+
//
//
// Device Info
@@ -5129,6 +5139,7 @@ public class TelephonyManager {
DATA_CONNECTING,
DATA_CONNECTED,
DATA_SUSPENDED,
+ DATA_DISCONNECTING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataState{}
@@ -5145,6 +5156,12 @@ public class TelephonyManager {
* traffic is temporarily unavailable. For example, in a 2G network,
* data activity may be suspended when a voice call arrives. */
public static final int DATA_SUSPENDED = 3;
+ /**
+ * Data connection state: Disconnecting.
+ *
+ * IP traffic may be available but will cease working imminently.
+ */
+ public static final int DATA_DISCONNECTING = 4;
/**
* Returns a constant indicating the current data connection state
@@ -5154,14 +5171,21 @@ public class TelephonyManager {
* @see #DATA_CONNECTING
* @see #DATA_CONNECTED
* @see #DATA_SUSPENDED
+ * @see #DATA_DISCONNECTING
*/
public int getDataState() {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return DATA_DISCONNECTED;
- return telephony.getDataStateForSubId(
+ int state = telephony.getDataStateForSubId(
getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
+ if (state == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ return TelephonyManager.DATA_CONNECTED;
+ }
+
+ return state;
} catch (RemoteException ex) {
// the phone process is restarting.
return DATA_DISCONNECTED;
@@ -5182,6 +5206,7 @@ public class TelephonyManager {
case DATA_CONNECTING: return "CONNECTING";
case DATA_CONNECTED: return "CONNECTED";
case DATA_SUSPENDED: return "SUSPENDED";
+ case DATA_DISCONNECTING: return "DISCONNECTING";
}
return "UNKNOWN(" + state + ")";
}
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index ac4c8ecea842..9f4d066ff8b4 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -594,4 +594,12 @@ interface ISms {
* @return true for success, false otherwise.
*/
boolean setSmscAddressOnIccEfForSubscriber(String smsc, int subId, String callingPackage);
+
+ /**
+ * Get the capacity count of sms on Icc card.
+ *
+ * @param subId for subId which getSmsCapacityOnIcc is queried.
+ * @return capacity of ICC
+ */
+ int getSmsCapacityOnIccForSubscriber(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 9865f76d2580..2430d8237d9c 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -211,4 +211,9 @@ public class ISmsImplBase extends ISms.Stub {
String smsc, int subId, String callingPackage) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public int getSmsCapacityOnIccForSubscriber(int subId) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ec54ecf56df..97b24aef4af5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -888,12 +888,13 @@ interface ITelephony {
/**
* @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
*/
- boolean setImsService(int slotId, boolean isCarrierImsService, String packageName);
+ boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+ in int[] featureTypes, in String packageName);
/**
* @return the package name of the carrier/device ImsService associated with this slot.
*/
- String getImsService(int slotId, boolean isCarrierImsService);
+ String getBoundImsServicePackage(int slotIndex, boolean isCarrierImsService, int featureType);
/**
* Get the MmTelFeature state attached to this subscription id.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
index f7f0f29fcb29..8640acca7c8c 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -16,14 +16,10 @@
package com.android.internal.telephony;
-import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.telephony.PreciseCallState;
import com.android.internal.telephony.PhoneConstants;
-import java.util.List;
-
public class PhoneConstantConversions {
/**
* Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
@@ -67,6 +63,8 @@ public class PhoneConstantConversions {
return TelephonyManager.DATA_CONNECTED;
case SUSPENDED:
return TelephonyManager.DATA_SUSPENDED;
+ case DISCONNECTING:
+ return TelephonyManager.DATA_DISCONNECTING;
default:
return TelephonyManager.DATA_DISCONNECTED;
}
@@ -84,6 +82,8 @@ public class PhoneConstantConversions {
return PhoneConstants.DataState.CONNECTED;
case TelephonyManager.DATA_SUSPENDED:
return PhoneConstants.DataState.SUSPENDED;
+ case TelephonyManager.DATA_DISCONNECTING:
+ return PhoneConstants.DataState.DISCONNECTING;
default:
return PhoneConstants.DataState.DISCONNECTED;
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index fde2c5a9a738..fadb5739e31f 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -46,6 +46,7 @@ public class PhoneConstants {
* <ul>
* <li>CONNECTED = IP traffic should be available</li>
* <li>CONNECTING = Currently setting up data connection</li>
+ * <li>DISCONNECTING = IP temporarily available</li>
* <li>DISCONNECTED = IP not available</li>
* <li>SUSPENDED = connection is created but IP traffic is
* temperately not available. i.e. voice call is in place
@@ -55,10 +56,15 @@ public class PhoneConstants {
@UnsupportedAppUsage(implicitMember =
"values()[Lcom/android/internal/telephony/PhoneConstants$DataState;")
public enum DataState {
- @UnsupportedAppUsage CONNECTED,
- @UnsupportedAppUsage CONNECTING,
- @UnsupportedAppUsage DISCONNECTED,
- @UnsupportedAppUsage SUSPENDED;
+ @UnsupportedAppUsage
+ CONNECTED,
+ @UnsupportedAppUsage
+ CONNECTING,
+ @UnsupportedAppUsage
+ DISCONNECTED,
+ @UnsupportedAppUsage
+ SUSPENDED,
+ DISCONNECTING;
};
public static final String STATE_KEY = "state";
@@ -91,20 +97,12 @@ public class PhoneConstants {
public static final String PHONE_NAME_KEY = "phoneName";
public static final String DATA_NETWORK_TYPE_KEY = "networkType";
- public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
public static final String DATA_APN_TYPE_KEY = "apnType";
public static final String DATA_APN_KEY = "apn";
- public static final String DATA_LINK_PROPERTIES_KEY = "linkProperties";
- public static final String DATA_NETWORK_CAPABILITIES_KEY = "networkCapabilities";
- public static final String DATA_IFACE_NAME_KEY = "iface";
- public static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
- public static final String DATA_NETWORK_ROAMING_KEY = "networkRoaming";
public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
- public static final String REASON_LINK_PROPERTIES_CHANGED = "linkPropertiesChanged";
-
/**
* Return codes for supplyPinReturnResult and
* supplyPukReturnResult APIs
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index b2c3fc79025b..8e1a78c56e0a 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -225,22 +225,6 @@ public class TelephonyIntents {
public static final String EXTRA_REBROADCAST_ON_UNLOCK= "rebroadcastOnUnlock";
/**
- * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
- * This is a sticky broadcast.
- * The intent will have the following extra values:</p>
- * <ul>
- * <li><em>time</em> - The time as a long in UTC milliseconds.</li>
- * </ul>
- *
- * <p class="note">
- * Requires the READ_PHONE_STATE permission.
- *
- * <p class="note">This is a protected intent that can only be sent
- * by the system.
- */
- public static final String ACTION_NETWORK_SET_TIME = "android.intent.action.NETWORK_SET_TIME";
-
- /**
* <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
* <p class="note">.
* This is to pop up a notice to show user that the phone is in emergency callback mode
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
deleted file mode 100644
index c16eafb41dde..000000000000
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Copyright (C) 2012 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.internal.telephony.gsm;
-
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.telephony.CbGeoUtils;
-import android.telephony.CbGeoUtils.Circle;
-import android.telephony.CbGeoUtils.Geometry;
-import android.telephony.CbGeoUtils.LatLng;
-import android.telephony.CbGeoUtils.Polygon;
-import android.telephony.Rlog;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.SubscriptionManager;
-import android.util.Pair;
-
-import com.android.internal.R;
-import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme;
-
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
- * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
- */
-public class GsmSmsCbMessage {
- private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
-
- private static final char CARRIAGE_RETURN = 0x0d;
-
- private static final int PDU_BODY_PAGE_LENGTH = 82;
-
- /** Utility class with only static methods. */
- private GsmSmsCbMessage() { }
-
- /**
- * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
- * so we have to show the pre-built messages to the user.
- *
- * @param context Device context
- * @param category ETWS message category defined in SmsCbConstants
- * @return ETWS text message in string. Return an empty string if no match.
- */
- private static String getEtwsPrimaryMessage(Context context, int category) {
- final Resources r = context.getResources();
- switch (category) {
- case ETWS_WARNING_TYPE_EARTHQUAKE:
- return r.getString(R.string.etws_primary_default_message_earthquake);
- case ETWS_WARNING_TYPE_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_tsunami);
- case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
- case ETWS_WARNING_TYPE_TEST_MESSAGE:
- return r.getString(R.string.etws_primary_default_message_test);
- case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
- return r.getString(R.string.etws_primary_default_message_others);
- default:
- return "";
- }
- }
-
- /**
- * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
- *
- * @param pdus PDU bytes
- * @slotIndex slotIndex for which received sms cb message
- */
- public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
- SmsCbLocation location, byte[][] pdus, int slotIndex)
- throws IllegalArgumentException {
- SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
- Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- int[] subIds = sm.getSubscriptionIds(slotIndex);
- if (subIds != null && subIds.length > 0) {
- subId = subIds[0];
- }
-
- long receivedTimeMillis = System.currentTimeMillis();
- if (header.isEtwsPrimaryNotification()) {
- // ETSI TS 23.041 ETWS Primary Notification message
- // ETWS primary message only contains 4 fields including serial number,
- // message identifier, warning type, and warning security information.
- // There is no field for the content/text so we get the text from the resources.
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
- header.getSerialNumber(), location, header.getServiceCategory(), null,
- getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
- SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
- header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex,
- subId);
- } else if (header.isUmtsFormat()) {
- // UMTS format has only 1 PDU
- byte[] pdu = pdus[0];
- Pair<String, String> cbData = parseUmtsBody(header, pdu);
- String language = cbData.first;
- String body = cbData.second;
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
- + 1 // number of pages
- + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
-
- // Has Warning Area Coordinates information
- List<Geometry> geometries = null;
- int maximumWaitingTimeSec = 255;
- if (pdu.length > wacDataOffset) {
- try {
- Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
- wacDataOffset);
- maximumWaitingTimeSec = wac.first;
- geometries = wac.second;
- } catch (Exception ex) {
- // Catch the exception here, the message will be considered as having no WAC
- // information which means the message will be broadcasted directly.
- Rlog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
- }
- }
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, body, priority,
- header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
- receivedTimeMillis, slotIndex, subId);
- } else {
- String language = null;
- StringBuilder sb = new StringBuilder();
- for (byte[] pdu : pdus) {
- Pair<String, String> p = parseGsmBody(header, pdu);
- language = p.first;
- sb.append(p.second);
- }
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, sb.toString(), priority,
- header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
- receivedTimeMillis, slotIndex, subId);
- }
- }
-
- /**
- * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
- *
- * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
- * to direct devices to perform a geo-fencing check on selected alerts.
- *
- * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
- * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
- * defined in TS 23.041.
- * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
- * WEA messages).
- * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
- * WEA message.
- * @param pdu cell broadcast pdu, including the header
- * @return {@link GeoFencingTriggerMessage} instance
- */
- public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
- try {
- // Header length + 1(number of page). ATIS-0700041 define the number of page of
- // geo-fencing trigger message is 1.
- int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
-
- BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
- int type = bitReader.read(4);
- int length = bitReader.read(7);
- // Skip the remained 5 bits
- bitReader.skip();
-
- int messageIdentifierCount = (length - 2) * 8 / 32;
- List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
- for (int i = 0; i < messageIdentifierCount; i++) {
- // Both messageIdentifier and serialNumber are 16 bits integers.
- // ATIS-0700041 Section 5.1.6
- int messageIdentifier = bitReader.read(16);
- int serialNumber = bitReader.read(16);
- cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
- }
- return new GeoFencingTriggerMessage(type, cbIdentifiers);
- } catch (Exception ex) {
- Rlog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
- return null;
- }
- }
-
- /**
- * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
- *
- * @param pdu Warning Area Coordinates TLV.
- * @param wacOffset the offset of Warning Area Coordinates TLV.
- * @return a pair with the first element is maximum wait time and the second is the broadcast
- * area. The default value of the maximum wait time is 255 which means use the device default
- * value.
- */
- private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
- byte[] pdu, int wacOffset) {
- // little-endian
- int wacDataLength = ((pdu[wacOffset + 1] & 0xff) << 8) | (pdu[wacOffset] & 0xff);
- int offset = wacOffset + 2;
-
- if (offset + wacDataLength > pdu.length) {
- throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
- + "least " + offset + wacDataLength + ", actual is " + pdu.length);
- }
-
- BitStreamReader bitReader = new BitStreamReader(pdu, offset);
-
- int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
-
- List<Geometry> geo = new ArrayList<>();
- int remainedBytes = wacDataLength;
- while (remainedBytes > 0) {
- int type = bitReader.read(4);
- int length = bitReader.read(10);
- remainedBytes -= length;
- // Skip the 2 remained bits
- bitReader.skip();
-
- switch (type) {
- case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
- maximumWaitTimeSec = bitReader.read(8);
- break;
- case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
- List<LatLng> latLngs = new ArrayList<>();
- // Each coordinate is represented by 44 bits integer.
- // ATIS-0700041 5.2.4 Coordinate coding
- int n = (length - 2) * 8 / 44;
- for (int i = 0; i < n; i++) {
- latLngs.add(getLatLng(bitReader));
- }
- // Skip the padding bits
- bitReader.skip();
- geo.add(new Polygon(latLngs));
- break;
- case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
- LatLng center = getLatLng(bitReader);
- // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
- // distance unit during geo-fencing.
- // ATIS-0700041 5.2.5 radius coding
- double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
- geo.add(new Circle(center, radius));
- break;
- default:
- throw new IllegalArgumentException("Unsupported geoType = " + type);
- }
- }
- return new Pair(maximumWaitTimeSec, geo);
- }
-
- /**
- * The coordinate is (latitude, longitude), represented by a 44 bits integer.
- * The coding is defined in ATIS-0700041 5.2.4
- * @param bitReader
- * @return coordinate (latitude, longitude)
- */
- private static LatLng getLatLng(BitStreamReader bitReader) {
- // wacLatitude = floor(((latitude + 90) / 180) * 2^22)
- // wacLongitude = floor(((longitude + 180) / 360) * 2^22)
- int wacLat = bitReader.read(22);
- int wacLng = bitReader.read(22);
-
- // latitude = wacLatitude * 180 / 2^22 - 90
- // longitude = wacLongitude * 360 / 2^22 -180
- return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
- }
-
- /**
- * Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
- *
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
- // Payload may contain multiple pages
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- String language = header.getDataCodingSchemeStructedData().language;
-
- if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
- * nrPages) {
- throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
- + nrPages + " pages");
- }
-
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < nrPages; i++) {
- // Each page is 82 bytes followed by a length octet indicating
- // the number of useful octets within those 82
- int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
- int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
- if (length > PDU_BODY_PAGE_LENGTH) {
- throw new IllegalArgumentException("Page length " + length
- + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
- }
-
- Pair<String, String> p = unpackBody(pdu, offset, length,
- header.getDataCodingSchemeStructedData());
- language = p.first;
- sb.append(p.second);
- }
- return new Pair(language, sb.toString());
-
- }
-
- /**
- * Parse and unpack the GSM body text according to the encoding in the data coding scheme.
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
- // Payload is one single page
- int offset = SmsCbHeader.PDU_HEADER_LENGTH;
- int length = pdu.length - offset;
- return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
- }
-
- /**
- * Unpack body text from the pdu using the given encoding, position and length within the pdu.
- *
- * @param pdu The pdu
- * @param offset Position of the first byte to unpack
- * @param length Number of bytes to unpack
- * @param dcs data coding scheme
- * @return a Pair of Strings containing the language and body of the message
- */
- private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
- DataCodingScheme dcs) {
- String body = null;
-
- String language = dcs.language;
- switch (dcs.encoding) {
- case SmsConstants.ENCODING_7BIT:
- body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
-
- if (dcs.hasLanguageIndicator && body != null && body.length() > 2) {
- // Language is two GSM characters followed by a CR.
- // The actual body text is offset by 3 characters.
- language = body.substring(0, 2);
- body = body.substring(3);
- }
- break;
-
- case SmsConstants.ENCODING_16BIT:
- if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) {
- // Language is two GSM characters.
- // The actual body text is offset by 2 bytes.
- language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
- offset += 2;
- length -= 2;
- }
-
- try {
- body = new String(pdu, offset, (length & 0xfffe), "utf-16");
- } catch (UnsupportedEncodingException e) {
- // Apparently it wasn't valid UTF-16.
- throw new IllegalArgumentException("Error decoding UTF-16 message", e);
- }
- break;
-
- default:
- break;
- }
-
- if (body != null) {
- // Remove trailing carriage return
- for (int i = body.length() - 1; i >= 0; i--) {
- if (body.charAt(i) != CARRIAGE_RETURN) {
- body = body.substring(0, i + 1);
- break;
- }
- }
- } else {
- body = "";
- }
-
- return new Pair<String, String>(language, body);
- }
-
- /** A class use to facilitate the processing of bits stream data. */
- private static final class BitStreamReader {
- /** The bits stream represent by a bytes array. */
- private final byte[] mData;
-
- /** The offset of the current byte. */
- private int mCurrentOffset;
-
- /**
- * The remained bits of the current byte which have not been read. The most significant
- * will be read first, so the remained bits are always the least significant bits.
- */
- private int mRemainedBit;
-
- /**
- * Constructor
- * @param data bit stream data represent by byte array.
- * @param offset the offset of the first byte.
- */
- BitStreamReader(byte[] data, int offset) {
- mData = data;
- mCurrentOffset = offset;
- mRemainedBit = 8;
- }
-
- /**
- * Read the first {@code count} bits.
- * @param count the number of bits need to read
- * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
- * greater than 32.
- */
- public int read(int count) throws IndexOutOfBoundsException {
- int val = 0;
- while (count > 0) {
- if (count >= mRemainedBit) {
- val <<= mRemainedBit;
- val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
- count -= mRemainedBit;
- mRemainedBit = 8;
- ++mCurrentOffset;
- } else {
- val <<= count;
- val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
- >> (mRemainedBit - count);
- mRemainedBit -= count;
- count = 0;
- }
- }
- return val;
- }
-
- /**
- * Skip the current bytes if the remained bits is less than 8. This is useful when
- * processing the padding or reserved bits.
- */
- public void skip() {
- if (mRemainedBit < 8) {
- mRemainedBit = 8;
- ++mCurrentOffset;
- }
- }
- }
-
- /**
- * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic.
- * @hide
- */
- public static final class GeoFencingTriggerMessage {
- /**
- * Indicate the list of active alerts share their warning area coordinates which means the
- * broadcast area is the union of the broadcast areas of the active alerts in this list.
- */
- public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
-
- public final int type;
- public final List<CellBroadcastIdentity> cbIdentifiers;
-
- GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
- this.type = type;
- this.cbIdentifiers = cbIdentifiers;
- }
-
- /**
- * Whether the trigger message indicates that the broadcast areas are shared between all
- * active alerts.
- * @return true if broadcast areas are to be shared
- */
- boolean shouldShareBroadcastArea() {
- return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
- }
-
- static final class CellBroadcastIdentity {
- public final int messageIdentifier;
- public final int serialNumber;
- CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
- this.messageIdentifier = messageIdentifier;
- this.serialNumber = serialNumber;
- }
- }
-
- @Override
- public String toString() {
- String identifiers = cbIdentifiers.stream()
- .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
- cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
- .collect(Collectors.joining(","));
- return "triggerType=" + type + " identifiers=" + identifiers;
- }
- }
-}
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 616b6b005f89..248c117d2e03 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -28,7 +28,7 @@ java_sdk_library {
":framework_native_aidl",
],
libs: [
- "framework-all",
+ "framework",
"app-compat-annotations",
"unsupportedappusage",
],
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 0208c3a0a0de..9d913b9861e5 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -211,6 +212,15 @@ public class MockContext extends Context {
throw new UnsupportedOperationException();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @Override
+ public File getCrateDir(@NonNull String crateId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public File getNoBackupFilesDir() {
throw new UnsupportedOperationException();
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index abe6c6152da5..daa85bd06669 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1098,4 +1098,28 @@ public class RollbackTest {
InstallUtils.dropShellPermissionIdentity();
}
}
+
+ /**
+ * Test we can't enable rollback for non-whitelisted app without
+ * TEST_MANAGE_ROLLBACKS permission
+ */
+ @Test
+ public void testNonRollbackWhitelistedApp() throws Exception {
+ try {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ } finally {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 7b289d8a7858..879ac64c6a55 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -40,6 +40,7 @@ import com.android.cts.install.lib.TestApp;
import com.android.cts.install.lib.Uninstall;
import com.android.cts.rollback.lib.Rollback;
import com.android.cts.rollback.lib.RollbackUtils;
+import com.android.internal.R;
import libcore.io.IoUtils;
@@ -341,6 +342,37 @@ public class StagedRollbackTest {
getNetworkStackPackageName())).isNull();
}
+ private static String getModuleMetadataPackageName() {
+ return InstrumentationRegistry.getInstrumentation().getContext()
+ .getResources().getString(R.string.config_defaultModuleMetadataProvider);
+ }
+
+ @Test
+ public void testRollbackWhitelistedApp_Phase1() throws Exception {
+ // Remove available rollbacks
+ String pkgName = getModuleMetadataPackageName();
+ RollbackUtils.getRollbackManager().expireRollbackForPackage(pkgName);
+ assertThat(RollbackUtils.getAvailableRollback(pkgName)).isNull();
+
+ // Overwrite existing permissions. We don't want TEST_MANAGE_ROLLBACKS which allows us
+ // to enable rollback for any app
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ // Re-install a whitelisted app with rollbacks enabled
+ String filePath = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getPackageInfo(pkgName, 0).applicationInfo.sourceDir;
+ TestApp app = new TestApp("ModuleMetadata", pkgName, -1, false, new File(filePath));
+ Install.single(app).setStaged().setEnableRollback()
+ .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+ }
+
+ @Test
+ public void testRollbackWhitelistedApp_Phase2() throws Exception {
+ assertThat(RollbackUtils.getAvailableRollback(getModuleMetadataPackageName())).isNotNull();
+ }
+
private static void runShellCommand(String cmd) {
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index e4a8febb8bce..07d829d2d0bb 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -175,6 +175,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
runPhase("testPreviouslyAbandonedRollbacks_Phase3");
}
+ /**
+ * Tests we can enable rollback for a whitelisted app.
+ */
+ @Test
+ public void testRollbackWhitelistedApp() throws Exception {
+ runPhase("testRollbackWhitelistedApp_Phase1");
+ getDevice().reboot();
+ runPhase("testRollbackWhitelistedApp_Phase2");
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index bd2ab5377311..03009aaeead2 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -738,7 +738,13 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config
const android::Res_value& res_value,
StringPool* dst_pool) {
if (type == ResourceType::kId) {
- return util::make_unique<Id>();
+ if (res_value.dataType != android::Res_value::TYPE_REFERENCE &&
+ res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) {
+ // plain "id" resources are actually encoded as dummy values (aapt1 uses an empty string,
+ // while aapt2 uses a false boolean).
+ return util::make_unique<Id>();
+ }
+ // fall through to regular reference deserialization logic
}
const uint32_t data = util::DeviceToHost32(res_value.data);
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 7733761eebcc..15c327852001 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -21,6 +21,7 @@ cc_binary_host {
name: "stats-log-api-gen",
srcs: [
"Collation.cpp",
+ "atoms_info_writer.cpp",
"java_writer.cpp",
"java_writer_q.cpp",
"main.cpp",
@@ -102,13 +103,19 @@ genrule {
cc_library {
name: "libstatslog",
host_supported: true,
- generated_sources: ["statslog.cpp"],
- generated_headers: ["statslog.h"],
+ generated_sources: [
+ "statslog.cpp",
+ ],
+ generated_headers: [
+ "statslog.h"
+ ],
cflags: [
"-Wall",
"-Werror",
],
- export_generated_headers: ["statslog.h"],
+ export_generated_headers: [
+ "statslog.h"
+ ],
shared_libs: [
"liblog",
"libcutils",
@@ -127,3 +134,4 @@ cc_library {
},
},
}
+
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 373adca994a1..fa556010646c 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -379,6 +379,7 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) {
int errorCount = 0;
const bool dbg = false;
+ int maxPushedAtomId = 2;
for (int i = 0; i < descriptor->field_count(); i++) {
const FieldDescriptor *atomField = descriptor->field(i);
@@ -447,8 +448,14 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) {
}
atoms->non_chained_decls.insert(nonChainedAtomDecl);
}
+
+ if (atomDecl.code < PULL_ATOM_START_ID && atomDecl.code > maxPushedAtomId) {
+ maxPushedAtomId = atomDecl.code;
+ }
}
+ atoms->maxPushedAtomId = maxPushedAtomId;
+
if (dbg) {
printf("signatures = [\n");
for (map<vector<java_type_t>, set<string>>::const_iterator it =
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 44746c96df1b..3efdd520d7f5 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -111,6 +111,7 @@ struct Atoms {
set<AtomDecl> decls;
set<AtomDecl> non_chained_decls;
map<vector<java_type_t>, set<string>> non_chained_signatures_to_modules;
+ int maxPushedAtomId;
};
/**
@@ -123,4 +124,4 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t>
} // namespace android
-#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H \ No newline at end of file
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
new file mode 100644
index 000000000000..54a9982bb5c2
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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.
+ */
+
+#include "atoms_info_writer.h"
+#include "utils.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace android {
+namespace stats_log_api_gen {
+
+static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+ fprintf(out, "struct StateAtomFieldOptions {\n");
+ fprintf(out, " std::vector<int> primaryFields;\n");
+ fprintf(out, " int exclusiveField;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "struct AtomsInfo {\n");
+ fprintf(out,
+ " const static std::set<int> "
+ "kTruncatingTimestampAtomBlackList;\n");
+ fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
+ fprintf(out,
+ " const static std::set<int> kAtomsWithAttributionChain;\n");
+ fprintf(out,
+ " const static std::map<int, StateAtomFieldOptions> "
+ "kStateAtomsFieldOptions;\n");
+ fprintf(out,
+ " const static std::map<int, std::vector<int>> "
+ "kBytesFieldAtoms;\n");
+ fprintf(out,
+ " const static std::set<int> kWhitelistedAtoms;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", atoms.maxPushedAtomId);
+
+}
+
+static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) {
+ std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
+ "audio_state_changed",
+ "call_state_changed",
+ "phone_signal_strength_changed",
+ "mobile_bytes_transfer_by_fg_bg",
+ "mobile_bytes_transfer"};
+ fprintf(out,
+ "const std::set<int> "
+ "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
+ for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
+ blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
+ fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out,
+ "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out,
+ "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->whitelisted) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ }
+ }
+
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
+ fprintf(out, " std::map<int, int> uidField;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->uidField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding uid field for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
+ make_constant_name(atom->name).c_str(), atom->uidField);
+ }
+
+ fprintf(out, " return uidField;\n");
+ fprintf(out, "};\n");
+
+ fprintf(out,
+ "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
+ "getAtomUidField();\n");
+
+ fprintf(out,
+ "static std::map<int, StateAtomFieldOptions> "
+ "getStateAtomFieldOptions() {\n");
+ fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
+ fprintf(out, " StateAtomFieldOptions opt;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding primary and exclusive fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " opt.primaryFields.clear();\n");
+ for (const auto& field : atom->primaryFields) {
+ fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
+ }
+
+ fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
+ fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
+ make_constant_name(atom->name).c_str());
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, StateAtomFieldOptions> "
+ "AtomsInfo::kStateAtomsFieldOptions = "
+ "getStateAtomFieldOptions();\n");
+
+ fprintf(out,
+ "static std::map<int, std::vector<int>> "
+ "getBinaryFieldAtoms() {\n");
+ fprintf(out, " std::map<int, std::vector<int>> options;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->binaryFields.size() == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding binary fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+
+ for (const auto& field : atom->binaryFields) {
+ fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
+ make_constant_name(atom->name).c_str(), field);
+ }
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, std::vector<int>> "
+ "AtomsInfo::kBytesFieldAtoms = "
+ "getBinaryFieldAtoms();\n");
+
+}
+
+int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespaceStr) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+ fprintf(out, "#pragma once\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <vector>\n");
+ fprintf(out, "#include <map>\n");
+ fprintf(out, "#include <set>\n");
+ fprintf(out, "\n");
+
+ write_namespace(out, namespaceStr);
+
+ write_atoms_info_header_body(out, atoms);
+
+ fprintf(out, "\n");
+ write_closing_namespace(out, namespaceStr);
+
+ return 0;
+}
+
+int write_atoms_info_cpp(FILE *out, const Atoms &atoms, const string& namespaceStr,
+ const string& importHeader, const string& statslogHeader) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <%s>\n", importHeader.c_str());
+ fprintf(out, "#include <%s>\n", statslogHeader.c_str());
+ fprintf(out, "\n");
+
+ write_namespace(out, namespaceStr);
+
+ write_atoms_info_cpp_body(out, atoms);
+
+ // Print footer
+ fprintf(out, "\n");
+ write_closing_namespace(out, namespaceStr);
+
+ return 0;
+}
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/tools/stats_log_api_gen/atoms_info_writer.h b/tools/stats_log_api_gen/atoms_info_writer.h
new file mode 100644
index 000000000000..bc677825181f
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Collation.h"
+
+#include <stdio.h>
+#include <string.h>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using namespace std;
+
+int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr,
+ const string& importHeader, const string& statslogHeader
+);
+
+int write_atoms_info_header(FILE* out, const Atoms& atoms, const string& namespaceStr);
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bc6d82ad267c..ad171da0511c 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -1,6 +1,7 @@
#include "Collation.h"
+#include "atoms_info_writer.h"
#if !defined(STATS_SCHEMA_LEGACY)
#include "java_writer.h"
#endif
@@ -18,8 +19,6 @@
#include <stdlib.h>
#include <string.h>
-#include "android-base/strings.h"
-
using namespace google::protobuf;
using namespace std;
@@ -28,152 +27,6 @@ namespace stats_log_api_gen {
using android::os::statsd::Atom;
-static void write_atoms_info_cpp(FILE *out, const Atoms &atoms) {
- std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
- "audio_state_changed",
- "call_state_changed",
- "phone_signal_strength_changed",
- "mobile_bytes_transfer_by_fg_bg",
- "mobile_bytes_transfer"};
- fprintf(out,
- "const std::set<int> "
- "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
- for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
- blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
- fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
- }
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- break;
- }
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->whitelisted) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
- fprintf(out, " std::map<int, int> uidField;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->uidField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding uid field for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
- make_constant_name(atom->name).c_str(), atom->uidField);
- }
-
- fprintf(out, " return uidField;\n");
- fprintf(out, "};\n");
-
- fprintf(out,
- "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
- "getAtomUidField();\n");
-
- fprintf(out,
- "static std::map<int, StateAtomFieldOptions> "
- "getStateAtomFieldOptions() {\n");
- fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
- fprintf(out, " StateAtomFieldOptions opt;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding primary and exclusive fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " opt.primaryFields.clear();\n");
- for (const auto& field : atom->primaryFields) {
- fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
- }
-
- fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
- fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
- make_constant_name(atom->name).c_str());
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, StateAtomFieldOptions> "
- "AtomsInfo::kStateAtomsFieldOptions = "
- "getStateAtomFieldOptions();\n");
-
- fprintf(out,
- "static std::map<int, std::vector<int>> "
- "getBinaryFieldAtoms() {\n");
- fprintf(out, " std::map<int, std::vector<int>> options;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->binaryFields.size() == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding binary fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
-
- for (const auto& field : atom->binaryFields) {
- fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
- make_constant_name(atom->name).c_str(), field);
- }
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, std::vector<int>> "
- "AtomsInfo::kBytesFieldAtoms = "
- "getBinaryFieldAtoms();\n");
-}
-
-// Writes namespaces for the cpp and header files, returning the number of namespaces written.
-void write_namespace(FILE* out, const string& cppNamespaces) {
- vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
- for (string cppNamespace : cppNamespaceVec) {
- fprintf(out, "namespace %s {\n", cppNamespace.c_str());
- }
-}
-
-// Writes namespace closing brackets for cpp and header files.
-void write_closing_namespace(FILE* out, const string& cppNamespaces) {
- vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
- for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
- fprintf(out, "} // namespace %s\n", it->c_str());
- }
-}
-
static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &attributionDecl,
const string& moduleName, const string& cppNamespace,
const string& importHeader) {
@@ -202,11 +55,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &at
fprintf(out, "const static bool kStatsdEnabled = false;\n");
fprintf(out, "#endif\n");
- // AtomsInfo is only used by statsd internally and is not needed for other modules.
- if (moduleName == DEFAULT_MODULE_NAME) {
- write_atoms_info_cpp(out, atoms);
- }
-
fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
fprintf(out, "static std::mutex mLogdRetryMutex;\n");
@@ -543,42 +391,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &at
return 0;
}
-static void write_cpp_usage(
- FILE* out, const string& method_name, const string& atom_code_name,
- const AtomDecl& atom, const AtomDecl &attributionDecl) {
- fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
- atom_code_name.c_str());
-
- for (vector<AtomField>::const_iterator field = atom.fields.begin();
- field != atom.fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str());
- } else {
- fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
- }
- }
- } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", const std::map<int, int32_t>& %s_int"
- ", const std::map<int, int64_t>& %s_long"
- ", const std::map<int, char const*>& %s_str"
- ", const std::map<int, float>& %s_float",
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str());
- } else {
- fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
- }
- }
- fprintf(out, ");\n");
-}
-
static void write_cpp_method_header(
FILE* out,
const string& method_name,
@@ -645,45 +457,8 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio
fprintf(out, " * API For logging statistics events.\n");
fprintf(out, " */\n");
fprintf(out, "\n");
- fprintf(out, "/**\n");
- fprintf(out, " * Constants for atom codes.\n");
- fprintf(out, " */\n");
- fprintf(out, "enum {\n");
- std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
- build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
-
- size_t i = 0;
- int maxPushedAtomId = 2;
- // Print atom constants
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
- string constant = make_constant_name(atom->name);
- fprintf(out, "\n");
- fprintf(out, " /**\n");
- fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
- write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
-
- auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
- if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
- write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
- attributionDecl);
- }
- fprintf(out, " */\n");
- char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
- fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
- if (atom->code < PULL_ATOM_START_ID && atom->code > maxPushedAtomId) {
- maxPushedAtomId = atom->code;
- }
- i++;
- }
- fprintf(out, "\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
+ write_native_atom_constants(out, atoms, attributionDecl, moduleName);
// Print constants for the enum values.
fprintf(out, "//\n");
@@ -723,36 +498,6 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio
fprintf(out, "};\n");
fprintf(out, "\n");
- // This metadata is only used by statsd, which uses the default libstatslog.
- if (moduleName == DEFAULT_MODULE_NAME) {
-
- fprintf(out, "struct StateAtomFieldOptions {\n");
- fprintf(out, " std::vector<int> primaryFields;\n");
- fprintf(out, " int exclusiveField;\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out, "struct AtomsInfo {\n");
- fprintf(out,
- " const static std::set<int> "
- "kTruncatingTimestampAtomBlackList;\n");
- fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
- fprintf(out,
- " const static std::set<int> kAtomsWithAttributionChain;\n");
- fprintf(out,
- " const static std::map<int, StateAtomFieldOptions> "
- "kStateAtomsFieldOptions;\n");
- fprintf(out,
- " const static std::map<int, std::vector<int>> "
- "kBytesFieldAtoms;");
- fprintf(out,
- " const static std::set<int> kWhitelistedAtoms;\n");
- fprintf(out, "};\n");
-
- fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
- maxPushedAtomId);
- }
-
// Print write methods
fprintf(out, "//\n");
fprintf(out, "// Write methods\n");
@@ -1235,15 +980,21 @@ print_usage()
fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n");
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS\n");
- fprintf(stderr, " --cpp FILENAME the header file to output\n");
- fprintf(stderr, " --header FILENAME the cpp file to output\n");
+ fprintf(stderr, " --cpp FILENAME the header file to output for write helpers\n");
+ fprintf(stderr, " --header FILENAME the cpp file to output for write helpers\n");
+ fprintf(stderr,
+ " --atomsInfoCpp FILENAME the header file to output for statsd metadata\n");
+ fprintf(stderr, " --atomsInfoHeader FILENAME the cpp file to output for statsd metadata\n");
fprintf(stderr, " --help this message\n");
fprintf(stderr, " --java FILENAME the java file to output\n");
fprintf(stderr, " --jni FILENAME the jni file to output\n");
fprintf(stderr, " --module NAME optional, module name to generate outputs for\n");
fprintf(stderr, " --namespace COMMA,SEP,NAMESPACE required for cpp/header with module\n");
fprintf(stderr, " comma separated namespace of the files\n");
- fprintf(stderr, " --importHeader NAME required for cpp/jni to say which header to import\n");
+ fprintf(stderr," --importHeader NAME required for cpp/jni to say which header to import "
+ "for write helpers\n");
+ fprintf(stderr," --atomsInfoImportHeader NAME required for cpp to say which header to import "
+ "for statsd metadata\n");
fprintf(stderr, " --javaPackage PACKAGE the package for the java file.\n");
fprintf(stderr, " required for java with module\n");
fprintf(stderr, " --javaClass CLASS the class name of the java class.\n");
@@ -1260,10 +1011,13 @@ run(int argc, char const*const* argv)
string headerFilename;
string javaFilename;
string jniFilename;
+ string atomsInfoCppFilename;
+ string atomsInfoHeaderFilename;
string moduleName = DEFAULT_MODULE_NAME;
string cppNamespace = DEFAULT_CPP_NAMESPACE;
string cppHeaderImport = DEFAULT_CPP_HEADER_IMPORT;
+ string atomsInfoCppHeaderImport = DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT;
string javaPackage = DEFAULT_JAVA_PACKAGE;
string javaClass = DEFAULT_JAVA_CLASS;
@@ -1335,14 +1089,38 @@ run(int argc, char const*const* argv)
return 1;
}
javaClass = argv[index];
+ } else if (0 == strcmp("--atomsInfoHeader", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoHeaderFilename = argv[index];
+ } else if (0 == strcmp("--atomsInfoCpp", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoCppFilename = argv[index];
+ } else if (0 == strcmp("--atomsInfoImportHeader", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoCppHeaderImport = argv[index];
}
+
index++;
}
if (cppFilename.size() == 0
&& headerFilename.size() == 0
&& javaFilename.size() == 0
- && jniFilename.size() == 0) {
+ && jniFilename.size() == 0
+ && atomsInfoHeaderFilename.size() == 0
+ && atomsInfoCppFilename.size() == 0) {
print_usage();
return 1;
}
@@ -1359,6 +1137,30 @@ run(int argc, char const*const* argv)
collate_atom(android::os::statsd::AttributionNode::descriptor(),
&attributionDecl, &attributionSignature);
+ // Write the atoms info .cpp file
+ if (atomsInfoCppFilename.size() != 0) {
+ FILE* out = fopen(atomsInfoCppFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoCppFilename.c_str());
+ return 1;
+ }
+ errorCount = android::stats_log_api_gen::write_atoms_info_cpp(
+ out, atoms, cppNamespace, atomsInfoCppHeaderImport, cppHeaderImport);
+ fclose(out);
+ }
+
+ // Write the atoms info .h file
+ if (atomsInfoHeaderFilename.size() != 0) {
+ FILE* out = fopen(atomsInfoHeaderFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoHeaderFilename.c_str());
+ return 1;
+ }
+ errorCount = android::stats_log_api_gen::write_atoms_info_header(out, atoms, cppNamespace);
+ fclose(out);
+ }
+
+
// Write the .cpp file
if (cppFilename.size() != 0) {
FILE* out = fopen(cppFilename.c_str(), "w");
diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp
index 141861d443b0..d6cfe95a34f5 100644
--- a/tools/stats_log_api_gen/utils.cpp
+++ b/tools/stats_log_api_gen/utils.cpp
@@ -16,9 +16,19 @@
#include "utils.h"
+#include "android-base/strings.h"
+
namespace android {
namespace stats_log_api_gen {
+static void build_non_chained_decl_map(const Atoms& atoms,
+ std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
+ for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+ atom != atoms.non_chained_decls.end(); atom++) {
+ decl_map->insert(std::make_pair(atom->code, atom));
+ }
+}
+
/**
* Turn lower and camel case into upper case with underscores.
*/
@@ -102,14 +112,98 @@ bool signature_needed_for_module(const set<string>& modules, const string& modul
return modules.find(moduleName) != modules.end();
}
-void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
- for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
- atom != atoms.non_chained_decls.end(); atom++) {
- decl_map->insert(std::make_pair(atom->code, atom));
+// Native
+// Writes namespaces for the cpp and header files, returning the number of namespaces written.
+void write_namespace(FILE* out, const string& cppNamespaces) {
+ vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+ for (string cppNamespace : cppNamespaceVec) {
+ fprintf(out, "namespace %s {\n", cppNamespace.c_str());
}
}
+// Writes namespace closing brackets for cpp and header files.
+void write_closing_namespace(FILE* out, const string& cppNamespaces) {
+ vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+ for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
+ fprintf(out, "} // namespace %s\n", it->c_str());
+ }
+}
+
+static void write_cpp_usage(
+ FILE* out, const string& method_name, const string& atom_code_name,
+ const AtomDecl& atom, const AtomDecl &attributionDecl) {
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
+ atom_code_name.c_str());
+
+ for (vector<AtomField>::const_iterator field = atom.fields.begin();
+ field != atom.fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ if (chainField.javaType == JAVA_TYPE_STRING) {
+ fprintf(out, ", const std::vector<%s>& %s",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str());
+ } else {
+ fprintf(out, ", const %s* %s, size_t %s_length",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str(), chainField.name.c_str());
+ }
+ }
+ } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
+ fprintf(out, ", const std::map<int, int32_t>& %s_int"
+ ", const std::map<int, int64_t>& %s_long"
+ ", const std::map<int, char const*>& %s_str"
+ ", const std::map<int, float>& %s_float",
+ field->name.c_str(),
+ field->name.c_str(),
+ field->name.c_str(),
+ field->name.c_str());
+ } else {
+ fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+ }
+ }
+ fprintf(out, ");\n");
+}
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& moduleName) {
+ fprintf(out, "/**\n");
+ fprintf(out, " * Constants for atom codes.\n");
+ fprintf(out, " */\n");
+ fprintf(out, "enum {\n");
+
+ std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
+ size_t i = 0;
+ // Print atom constants
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ // Skip if the atom is not needed for the module.
+ if (!atom_needed_for_module(*atom, moduleName)) {
+ continue;
+ }
+ string constant = make_constant_name(atom->name);
+ fprintf(out, "\n");
+ fprintf(out, " /**\n");
+ fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
+ write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+ write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+ attributionDecl);
+ }
+ fprintf(out, " */\n");
+ char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
+ fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
+ i++;
+ }
+ fprintf(out, "\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+}
+
// Java
void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName) {
fprintf(out, " // Constants for atom codes.\n");
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index e860fa9045cb..a89387f00bce 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -33,6 +33,7 @@ using namespace std;
const string DEFAULT_MODULE_NAME = "DEFAULT";
const string DEFAULT_CPP_NAMESPACE = "android,util";
const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h";
+const string DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT = "atoms_info.h";
const string DEFAULT_JAVA_PACKAGE = "android.util";
const string DEFAULT_JAVA_CLASS = "StatsLogInternal";
@@ -49,8 +50,14 @@ bool atom_needed_for_module(const AtomDecl& atomDecl, const string& moduleName);
bool signature_needed_for_module(const set<string>& modules, const string& moduleName);
-void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map);
+// Common Native helpers
+void write_namespace(FILE* out, const string& cppNamespaces);
+
+void write_closing_namespace(FILE* out, const string& cppNamespaces);
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& moduleName
+);
// Common Java helpers.
void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName);
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 26064cbc2fb1..08115ecb6b2b 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -58,21 +58,6 @@ java_library {
}
}
-metalava_wifi_docs_args =
- "--hide-package com.android.server " +
- "--error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide MissingPermission " +
- "--hide BroadcastBehavior " +
- "--hide HiddenSuperclass " +
- "--hide DeprecationMismatch " +
- "--hide UnavailableSymbol " +
- "--hide SdkConstant " +
- "--hide HiddenTypeParameter " +
- "--hide Todo --hide Typo " +
- "--hide HiddenTypedefConstant " +
- "--show-annotation android.annotation.SystemApi "
-
droidstubs {
name: "framework-wifi-stubs-srcs",
srcs: [
@@ -82,7 +67,7 @@ droidstubs {
aidl: {
include_dirs: ["frameworks/base/core/java"],
},
- args: metalava_wifi_docs_args,
+ defaults: [ "framework-module-stubs-defaults-systemapi" ],
sdk_version: "core_current",
libs: ["android_system_stubs_current"],
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 495b1bb9ba82..c9bca4f20f03 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -48,21 +48,29 @@ public class WifiP2pConfig implements Parcelable {
*/
public WpsInfo wps;
- /**
- * The network name of a group, should be configured by helper method
- */
+ /** Get the network name of this P2P configuration, or null if unset. */
+ @Nullable
+ public String getNetworkName() {
+ return networkName;
+ }
+
/** @hide */
public String networkName = "";
- /**
- * The passphrase of a group, should be configured by helper method
- */
+ /** Get the passphrase of this P2P configuration, or null if unset. */
+ @Nullable
+ public String getPassphrase() {
+ return passphrase;
+ }
+
/** @hide */
public String passphrase = "";
- /**
- * The required band for Group Owner
- */
+ /** Get the required band for the group owner. */
+ public int getGroupOwnerBand() {
+ return groupOwnerBand;
+ }
+
/** @hide */
public int groupOwnerBand = GROUP_OWNER_BAND_AUTO;
@@ -123,6 +131,15 @@ public class WifiP2pConfig implements Parcelable {
@UnsupportedAppUsage
public int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ /**
+ * Get the network ID of this P2P configuration.
+ * @return either a non-negative network ID, or one of {@link WifiP2pGroup#PERSISTENT_NET_ID} or
+ * {@link WifiP2pGroup#TEMPORARY_NET_ID}.
+ */
+ public int getNetworkId() {
+ return netId;
+ }
+
public WifiP2pConfig() {
//set defaults
wps = new WpsInfo();
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index f9d1266cf804..d8c50f2ed5f8 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -39,18 +39,15 @@ public class WifiP2pGroup implements Parcelable {
/**
* The temporary network id.
- *
- * @hide
+ * @see #getNetworkId()
*/
- @UnsupportedAppUsage
public static final int TEMPORARY_NET_ID = -1;
/**
* The persistent network id.
* If a matching persistent profile is found, use it.
* Otherwise, create a new persistent profile.
- *
- * @hide
+ * @see #getNetworkId()
*/
public static final int PERSISTENT_NET_ID = -2;